From: Stephan Soller <stephan.soller@helionweb.de>
Date: Thu, 9 Feb 2017 21:29:13 +0000 (+0100)
Subject: Added first version of the Slim GL library.
X-Git-Url: https://git.owens.tech/about.html/about.html/git?a=commitdiff_plain;h=a41b1858f5757d4c97ee19beb592687180e331f2;p=forks%2Fsingle-header-file-c-libs.git

Added first version of the Slim GL library.
---

diff --git a/slim_gl.c b/slim_gl.c
new file mode 100755
index 0000000..3bf1748
--- /dev/null
+++ b/slim_gl.c
@@ -0,0 +1,1199 @@
+// For fmemstream()
+#define _GNU_SOURCE
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "sgl.h"
+
+
+static bool gl_error(const char* description, ...);
+static GLuint create_and_compile_shader(GLenum shader_type, const char* code, GLint code_size, FILE* error_stream);
+
+#ifndef _SGL_UTILS
+void* fload(const char* filename, size_t* size);
+#endif
+
+
+//
+// Shader functions
+//
+
+GLuint program_new(const char* vertex_shader_file, const char* fragment_shader_file, FILE* compiler_message_stream) {
+	char* vertex_shader_code = fload(vertex_shader_file, NULL);
+	if (vertex_shader_code == NULL) {
+		if (compiler_message_stream) fprintf(compiler_message_stream, "Failed to read vertex shader file %s: %s\n", vertex_shader_file, strerror(errno));
+		return 0;
+	}
+	
+	char* fragment_shader_code = fload(fragment_shader_file, NULL);
+	if (fragment_shader_code == NULL) {
+		free(vertex_shader_code);
+		if (compiler_message_stream) fprintf(compiler_message_stream, "Failed to read fragment shader file %s: %s\n", fragment_shader_file, strerror(errno));
+		return 0;
+	}
+	
+	GLuint program = program_new_from_string(vertex_shader_code, fragment_shader_code, compiler_message_stream);
+	
+	free(vertex_shader_code);
+	free(fragment_shader_code);
+	
+	return program;
+}
+
+GLuint program_new_from_string(const char* vertex_shader_code, const char* fragment_shader_code, FILE* compiler_message_stream) {
+	GLuint vertex_shader = create_and_compile_shader(GL_VERTEX_SHADER, vertex_shader_code, -1, compiler_message_stream);
+	GLuint fragment_shader = create_and_compile_shader(GL_FRAGMENT_SHADER, fragment_shader_code, -1, compiler_message_stream);
+	if (vertex_shader == 0 || fragment_shader == 0)
+		goto shaders_failed;
+	
+	GLuint prog = glCreateProgram();
+	glAttachShader(prog, vertex_shader);
+	glAttachShader(prog, fragment_shader);
+	glLinkProgram(prog);
+	
+	GLint result = GL_TRUE;
+	glGetProgramiv(prog, GL_LINK_STATUS, &result);
+	if (result == GL_FALSE){
+		if (compiler_message_stream) {
+			glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &result);
+			char buffer[result];
+			glGetProgramInfoLog(prog, result, NULL, buffer);
+			fprintf(compiler_message_stream, "Linking of vertex and pixel shader failed:\n%s\n", buffer);
+			program_inspect(prog, compiler_message_stream);
+		}
+		goto program_failed;
+	}
+	
+	return prog;
+	
+	program_failed:
+		if (prog)
+			glDeleteProgram(prog);
+		
+	shaders_failed:
+		if (vertex_shader)
+			glDeleteShader(vertex_shader);
+		if (fragment_shader)
+			glDeleteShader(fragment_shader);
+	
+	return 0;
+}
+
+
+/**
+ * Loads and compiles a source code file as a shader.
+ * 
+ * Returns the shaders GL object id on success or 0 on error. Compiler errors in the shader
+ * are appended to the error buffer.
+ */
+static GLuint create_and_compile_shader(GLenum shader_type, const char* code, GLint code_size, FILE* error_stream) {
+	GLuint shader = glCreateShader(shader_type);
+	glShaderSource(shader, 1, (const char*[]){ code }, (const int[]){ code_size });
+	glCompileShader(shader);
+	
+	GLint result = GL_TRUE;
+	glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
+	if (result)
+		return shader;
+	
+	if (error_stream) {
+		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &result);
+		char buffer[result];
+		glGetShaderInfoLog(shader, result, NULL, buffer);
+		fprintf(error_stream, "Failed to compile shader:\n%s\n", buffer);
+	}
+	
+	glDeleteShader(shader);
+	return 0;
+}
+
+/**
+ * Destorys the specified OpenGL program and all shaders attached to it.
+ */
+void program_destroy(GLuint program) {
+	GLint shader_count = 0;
+	glGetProgramiv(program, GL_ATTACHED_SHADERS, &shader_count);
+	
+	GLuint shaders[shader_count];
+	glGetAttachedShaders(program, shader_count, NULL, shaders);
+	
+	glDeleteProgram(program);
+	for(ssize_t i = 0; i < shader_count; i++)
+		glDeleteShader(shaders[i]);
+}
+
+/**
+ * Displays all attributes and uniforms of the OpenGL program on the output_stream.
+ * If output_stream is NULL stderr will be used instead.
+ */
+void program_inspect(GLuint program, FILE* output_stream) {
+	const char* type_to_string(GLenum type) {
+		switch(type){
+			case GL_FLOAT: return "float​";
+			case GL_FLOAT_VEC2: return "vec2​";
+			case GL_FLOAT_VEC3: return "vec3​";
+			case GL_FLOAT_VEC4: return "vec4​";
+			case GL_INT: return "int​";
+			case GL_INT_VEC2: return "ivec2​";
+			case GL_INT_VEC3: return "ivec3​";
+			case GL_INT_VEC4: return "ivec4​";
+			case GL_UNSIGNED_INT: return "unsigned int​";
+			case GL_UNSIGNED_INT_VEC2: return "uvec2​";
+			case GL_UNSIGNED_INT_VEC3: return "uvec3​";
+			case GL_UNSIGNED_INT_VEC4: return "uvec4​";
+			case GL_BOOL: return "bool​";
+			case GL_BOOL_VEC2: return "bvec2​";
+			case GL_BOOL_VEC3: return "bvec3​";
+			case GL_BOOL_VEC4: return "bvec4​";
+			case GL_FLOAT_MAT2: return "mat2​";
+			case GL_FLOAT_MAT3: return "mat3​";
+			case GL_FLOAT_MAT4: return "mat4​";
+			case GL_FLOAT_MAT2x3: return "mat2x3​";
+			case GL_FLOAT_MAT2x4: return "mat2x4​";
+			case GL_FLOAT_MAT3x2: return "mat3x2​";
+			case GL_FLOAT_MAT3x4: return "mat3x4​";
+			case GL_FLOAT_MAT4x2: return "mat4x2​";
+			case GL_FLOAT_MAT4x3: return "mat4x3​";
+			case GL_SAMPLER_1D: return "sampler1D​";
+			case GL_SAMPLER_2D: return "sampler2D​";
+			case GL_SAMPLER_3D: return "sampler3D​";
+			case GL_SAMPLER_CUBE: return "samplerCube​";
+			case GL_SAMPLER_1D_SHADOW: return "sampler1DShadow​";
+			case GL_SAMPLER_2D_SHADOW: return "sampler2DShadow​";
+			case GL_SAMPLER_1D_ARRAY: return "sampler1DArray​";
+			case GL_SAMPLER_2D_ARRAY: return "sampler2DArray​";
+			case GL_SAMPLER_1D_ARRAY_SHADOW: return "sampler1DArrayShadow​";
+			case GL_SAMPLER_2D_ARRAY_SHADOW: return "sampler2DArrayShadow​";
+			case GL_SAMPLER_2D_MULTISAMPLE: return "sampler2DMS​";
+			case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return "sampler2DMSArray​";
+			case GL_SAMPLER_CUBE_SHADOW: return "samplerCubeShadow​";
+			case GL_SAMPLER_BUFFER: return "samplerBuffer​";
+			case GL_SAMPLER_2D_RECT: return "sampler2DRect​";
+			case GL_SAMPLER_2D_RECT_SHADOW: return "sampler2DRectShadow​";
+			case GL_INT_SAMPLER_1D: return "isampler1D​";
+			case GL_INT_SAMPLER_2D: return "isampler2D​";
+			case GL_INT_SAMPLER_3D: return "isampler3D​";
+			case GL_INT_SAMPLER_CUBE: return "isamplerCube​";
+			case GL_INT_SAMPLER_1D_ARRAY: return "isampler1DArray​";
+			case GL_INT_SAMPLER_2D_ARRAY: return "isampler2DArray​";
+			case GL_INT_SAMPLER_2D_MULTISAMPLE: return "isampler2DMS​";
+			case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "isampler2DMSArray​";
+			case GL_INT_SAMPLER_BUFFER: return "isamplerBuffer​";
+			case GL_INT_SAMPLER_2D_RECT: return "isampler2DRect​";
+			case GL_UNSIGNED_INT_SAMPLER_1D: return "usampler1D​";
+			case GL_UNSIGNED_INT_SAMPLER_2D: return "usampler2D​";
+			case GL_UNSIGNED_INT_SAMPLER_3D: return "usampler3D​";
+			case GL_UNSIGNED_INT_SAMPLER_CUBE: return "usamplerCube​";
+			case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return "usampler2DArray​";
+			case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return "usampler2DArray​";
+			case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return "usampler2DMS​";
+			case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "usampler2DMSArray​";
+			case GL_UNSIGNED_INT_SAMPLER_BUFFER: return "usamplerBuffer​";
+			case GL_UNSIGNED_INT_SAMPLER_2D_RECT: return "usampler2DRect​";
+			default: return "unknown";
+		}
+	}
+	
+	if (output_stream == NULL)
+		output_stream = stderr;
+	
+	GLint size;
+	GLenum type;
+	{
+		GLint attrib_count = 0, buffer_size = 0;
+		glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attrib_count);
+		fprintf(output_stream, "%d attributes:\n", attrib_count);
+		glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &buffer_size);
+		char buffer[buffer_size];
+		
+		for(ssize_t i = 0; i < attrib_count; i++){
+			glGetActiveAttrib(program, i, buffer_size, NULL, &size, &type, buffer);
+			fprintf(output_stream, "- %s %s", buffer, type_to_string(type));
+			if (size != 1)
+				fprintf(output_stream, "[%d]", size);
+			fprintf(output_stream, "\n");
+		}
+	}
+	
+	{
+		GLint uniform_count = 0, buffer_size = 0;
+		glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count);
+		fprintf(output_stream, "%d uniforms:\n", uniform_count);
+		glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &buffer_size);
+		char buffer[buffer_size];
+		
+		for(ssize_t i = 0; i < uniform_count; i++){
+			glGetActiveUniform(program, i, buffer_size, NULL, &size, &type, buffer);
+			fprintf(output_stream, "- %s %s", buffer, type_to_string(type));
+			if (size != 1)
+				fprintf(output_stream, "[%d]", size);
+			fprintf(output_stream, "\n");
+		}
+	}
+}
+
+
+//
+// Buffer functions
+//
+
+/**
+ * Creates a new vertex buffer with the specified size and initial data uploaded. The initial data
+ * is uploaded with the GL_STATIC_DRAW usage, meant to be used for model data that does not change.
+ * 
+ * If `data` is `NULL` but a size is given the buffer will be allocated but no data is uploaded.
+ * If `size` is `0` only the OpenGL object is created but nothing is allocated.
+ * 
+ * Returns the vertex buffer on success or `0` on error.
+ */
+GLuint buffer_new(const void* data, size_t size) {
+	GLuint buffer = 0;
+	glGenBuffers(1, &buffer);
+	if (buffer == 0)
+		return 0;
+	
+	if (size > 0)
+		buffer_update(buffer, data, size, GL_STATIC_DRAW);
+	
+	return buffer;
+}
+
+void buffer_destroy(GLuint buffer) {
+	glDeleteBuffers(1, (const GLuint[]){ buffer });
+}
+
+/**
+ * Updates the vertex buffer with new data. The `usage` parameter is the same as of the
+ * `glBufferData()` function: GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY, GL_STATIC_DRAW,
+ * GL_STATIC_READ, GL_STATIC_COPY, GL_DYNAMIC_DRAW, GL_DYNAMIC_READ or GL_DYNAMIC_COPY.
+ */
+void buffer_update(GLuint buffer, const void* data, size_t size, GLenum usage) {
+	glBindBuffer(GL_ARRAY_BUFFER, buffer);
+	glBufferData(GL_ARRAY_BUFFER, size, data, usage);
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+
+//
+// Texture functions
+//
+
+/**
+ * Creates a normal 2D texture.
+ * 
+ * The textures minifing filter is set to GL_LINEAR_MIPMAP_LINEAR for better quality (default value
+ * is GL_NEAREST_MIPMAP_LINEAR).
+ */
+GLuint texture_new(const void* data, size_t width, size_t height, uint8_t components) {
+	GLenum internal_format = 0, data_format = 0;
+	switch(components) {
+		case 1: internal_format = GL_R8;    data_format = GL_RED;  break;
+		case 2: internal_format = GL_RG8;   data_format = GL_RG;   break;
+		case 3: internal_format = GL_RGB8;  data_format = GL_RGB;  break;
+		case 4: internal_format = GL_RGBA8; data_format = GL_RGBA; break;
+		default: return 0;
+	}
+	
+	GLuint texture = 0;
+	glGenTextures(1, &texture);
+	if (texture == 0)
+		return 0;
+	
+	GLsizei mipmap_levels = 1;
+	size_t w = width, h = height;
+	while (w > 1 || h > 1) {
+		mipmap_levels++;
+		w /= 2;
+		h /= 2;
+	}
+	
+	glBindTexture(GL_TEXTURE_2D, texture);
+	glTexStorage2D(GL_TEXTURE_2D, mipmap_levels, internal_format, width, height);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+	if (data) {
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, data_format, GL_UNSIGNED_BYTE, data);
+		glGenerateMipmap(GL_TEXTURE_2D);
+	}
+	glBindTexture(GL_TEXTURE_2D, 0);
+	
+	return texture;
+}
+
+
+/**
+ * Creates and uploads a rectangular texture of the specified dimensions and with the specified
+ * number of components (1, 2, 3 or 4). If data is NULL the texture will be allocated but no data
+ * is uploaded.
+ * 
+ * To make the API easier this function only supports textures with 8 bits per pixel (GL_R8,
+ * GL_RG8, GL_RGB8 and GL_RGBA8).
+ * 
+ * Returns the texture object on success or `0` on error.
+ */
+GLuint texture_new_rect(const void* data, size_t width, size_t height, uint8_t components) {
+	GLenum internal_format = 0, data_format = 0;
+	switch(components) {
+		case 1: internal_format = GL_R8;    data_format = GL_RED;  break;
+		case 2: internal_format = GL_RG8;   data_format = GL_RG;   break;
+		case 3: internal_format = GL_RGB8;  data_format = GL_RGB;  break;
+		case 4: internal_format = GL_RGBA8; data_format = GL_RGBA; break;
+		default: return 0;
+	}
+	
+	GLuint texture = 0;
+	glGenTextures(1, &texture);
+	if (texture == 0)
+		return 0;
+	
+	glBindTexture(GL_TEXTURE_RECTANGLE, texture);
+	glTexStorage2D(GL_TEXTURE_RECTANGLE, 1, internal_format, width, height);
+	if (data)
+		glTexSubImage2D(GL_TEXTURE_RECTANGLE, 0, 0, 0, width, height, data_format, GL_UNSIGNED_BYTE, data);
+	glBindTexture(GL_TEXTURE_RECTANGLE, 0);
+	
+	return texture;
+}
+
+void texture_destroy(GLuint texture) {
+	glDeleteTextures(1, (const GLuint[]){ texture });
+}
+
+/**
+ * Uploads new data for the specified texture. The data is expected to be as large as the entire
+ * texture and to have the same number of components.
+ */
+void texture_update(GLuint texture, const void* data) {
+	GLenum texture_type = GL_TEXTURE_2D;
+	glBindTexture(texture_type, texture);
+	if ( glGetError() == GL_INVALID_OPERATION ) {
+		texture_type = GL_TEXTURE_RECTANGLE;
+		glBindTexture(texture_type, texture);
+	}
+	
+	GLint width = 0, height = 0, internal_format = 0;
+	glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_WIDTH, &width);
+	glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_HEIGHT, &height);
+	glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
+	
+	GLenum data_format = 0;
+	switch(internal_format) {
+		case GL_R8:    data_format = GL_RED;  break;
+		case GL_RG8:   data_format = GL_RG;   break;
+		case GL_RGB8:  data_format = GL_RGB;  break;
+		case GL_RGBA8: data_format = GL_RGBA; break;
+	}
+	
+	if (data_format != 0) {
+		glTexSubImage2D(texture_type, 0, 0, 0, width, height, data_format, GL_UNSIGNED_BYTE, data);
+		if (texture_type == GL_TEXTURE_2D)
+			glGenerateMipmap(GL_TEXTURE_2D);
+	}
+	
+	glBindTexture(texture_type, 0);
+}
+
+
+//
+// Frame buffer functions
+//
+
+GLuint framebuffer_new(GLuint color_buffer_texture) {
+	GLuint framebuffer = 0;
+	glGenFramebuffers(1, &framebuffer);
+	
+	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+	glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, color_buffer_texture, 0);
+	
+	if ( glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE ) {
+		glDeleteFramebuffers(1, &framebuffer);
+		return 0;
+	}
+	
+	return framebuffer;
+}
+
+void framebuffer_destroy(GLuint framebuffer) {
+	glDeleteFramebuffers(1, &framebuffer);
+}
+
+/**
+ * Note: Binds the read and draw framebuffers to GL_READ_FRAMEBUFFER and GL_DRAW_FRAMEBUFFER if they're
+ * not already bound there. The bindings are left there so repeated calls don't need to bind them again.
+ */
+void framebuffer_blit(GLuint read_framebuffer, GLint rx, GLint ry, GLint rw, GLint rh, GLuint draw_framebuffer, GLint dx, GLint dy, GLint dw, GLint dh) {
+	GLint read_fb = 0, draw_fb = 0;
+	glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read_fb);
+	glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw_fb);
+	
+	if ((GLuint)read_fb != read_framebuffer)
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer);
+	if ((GLuint)draw_fb != draw_framebuffer)
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer);
+	
+	glBlitFramebuffer(rx, ry, rx+rw, ry+rh, dx, dy, dx+dw, dy+dh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+}
+
+void framebuffer_bind(GLuint framebuffer) {
+	// Check framebuffer binding, and bind the target framebuffer if necessary
+	GLint bound_framebuffer = 0;
+	glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &bound_framebuffer);
+	if ((GLuint)bound_framebuffer != framebuffer) {
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+		gl_error("Failed to bind framebuffer %u. glBindFramebuffer()", framebuffer);
+		
+		GLint type = 0;
+		glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
+		if (type == GL_TEXTURE) {
+			GLint color_buffer = 0, old_texture_binding = 0, width = 0, height = 0;
+			glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &color_buffer);
+			
+			GLenum texture_type = GL_TEXTURE_2D;
+			glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding);
+			glBindTexture(texture_type, color_buffer);
+			if ( glGetError() == GL_INVALID_OPERATION ) {
+				glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &old_texture_binding);
+				texture_type = GL_TEXTURE_RECTANGLE;
+				glBindTexture(texture_type, color_buffer);
+			}
+			
+			glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_WIDTH, &width);
+			glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_HEIGHT, &height);
+			glBindTexture(texture_type, old_texture_binding);
+			
+			glViewport(0, 0, width, height);
+		}
+	}
+}
+
+
+//
+// Drawing functions
+//
+
+typedef struct {
+	char name[128];
+	char modifiers[16];
+	uint8_t modifier_length;
+	uint16_t type;
+} directive_t, *directive_p;
+
+typedef struct {
+	GLenum opengl_type;
+	GLint type_size, components;
+	bool normalized, upload_as_int;
+} attribute_info_t, *attribute_info_p;
+
+static int next_directive(const char** bindings, directive_p output_directive);
+static bool parse_attribute_directive(directive_p directive, attribute_info_p attribute_info);
+
+
+/**
+ * Form of directives: <attribute or uniform name> %<dimensions><modifiers><type>
+ * e.g. "projection %4tM"
+ * 
+ * Uniforms (and textures): uppercase types
+ * 
+ * F (float)
+ * 	%1F  glUniform1fv
+ * 	%2F  glUniform2fv
+ * 	%3F  glUniform3fv
+ * 	%4F  glUniform4fv
+ * I (integer)
+ * 	%1I  glUniform1iv
+ * 	%2I  glUniform2iv
+ * 	%3I  glUniform3iv
+ * 	%4I  glUniform4iv
+ * U (unsigned integer)
+ * 	%1U  glUniform1ui
+ * 	%2U  glUniform2ui
+ * 	%3U  glUniform3ui
+ * 	%4U  glUniform4ui
+ * M (matrix)
+ * 	%2M    glUniformMatrix2fv
+ * 	%2x3M  glUniformMatrix2x3fv
+ * 	%2x4M  glUniformMatrix2x4fv
+ * 	%3M    glUniformMatrix3fv
+ * 	%3x2M  glUniformMatrix3x2fv
+ * 	%3x4M  glUniformMatrix3x4fv
+ * 	%4M    glUniformMatrix4fv
+ * 	%4x2M  glUniformMatrix4x2fv
+ * 	%4x3M  glUniformMatrix4x3fv
+ * 	
+ * 	Modifiers:
+ * 		t  transpose matrix
+ * 
+ * T (textures)
+ * 	%T  bind a GL_TEXTURE_2D texture to a sampler uniform
+ * 	
+ * 	Modifiers:
+ * 		r  texture is a GL_TEXTURE_RECTANGLE texture
+ * 
+ * 
+ * Attributes: lower case types
+ * 	
+ * 	The first attribute consumes one argument that has to be an OpenGL buffer object.
+ * 	This buffer is used for all following attributes or until an ";" is encountered.
+ * 	The next attribute after ";" consumes a new buffer argument.
+ * 	Stride and offsets of attributes are calculated automatically.
+ * 	The attribute name "_" is used for padding.
+ * 
+ * 	Dimensions: 1, 2, 3, 4
+ * 
+ * f  GL_FLOAT
+ * 	h  GL_HALF_FLOAT
+ * 	f  GL_FIXED
+ * b  GL_BYTE
+ * 	u  GL_UNSIGNED_BYTE
+ * 	n  normalized
+ * 	i  upload as int (use glVertexAttribIPointer())
+ * 
+ * s  GL_SHORT
+ * 	u  GL_UNSIGNED_SHORT
+ * 	n  normalized
+ * 	i  upload as int (use glVertexAttribIPointer())
+ * 
+ * i  GL_INT
+ * 	u  GL_UNSIGNED_INT
+ * 	n  normalized
+ * 	i  upload as int (use glVertexAttribIPointer())
+ * 
+ * Global options: start with "$" instead of "%"
+ * 
+ * 	$I  draw with an index buffer of GL_UNSIGNED_INT indices
+ *    b  indices are of type GL_UNSIGNED_BYTE
+ *    s  indices are of type GL_UNSIGNED_SHORT
+ * 
+ * Ideas (not yet implemented):
+ * 
+ * 	$F  render into that frame buffer
+ * 	$E  output error messages into that FILE* stream
+ */
+int render(GLenum primitive, GLuint program, const char* bindings, ...) {
+	va_list args;
+	directive_t d;
+	attribute_info_t ai;
+	size_t active_textures = 0;
+	GLsizei current_buffer_stride = 0;
+	size_t current_buffer_offset = 0;
+	GLint current_buffer_size = 0;
+	uint32_t vertecies_to_render = UINT32_MAX;
+	bool use_index_buffer = false;
+	GLenum index_buffer_type = 0;
+	uint32_t indices_to_render = 0;
+	//GLuint use_framebuffer = 0;
+	
+	GLuint vertex_array_object = 0;
+	glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&vertex_array_object);
+	if (vertex_array_object == 0) {
+		glGenVertexArrays(1, &vertex_array_object);
+		glBindVertexArray(vertex_array_object);
+		gl_error("Failed to generate and bind a new vertex array object. glBindVertexArray()");
+	}
+	
+	glUseProgram(program);
+	if ( gl_error("Can't use OpenGL program for drawing. glUseProgram()") )
+		return -1;
+	
+	va_start(args, bindings);
+	const char* setup_pass_bindings = bindings;
+	while( next_directive(&setup_pass_bindings, &d) ) {
+		if (d.type == ';') {
+			// User no longer wants to use the current buffer for attributes. So reset all the buffer
+			// stuff. If a new attribute directive comes around it will consume the next buffer.
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			gl_error("Failed to unbind vertex buffer. glBindBuffer(GL_ARRAY_BUFFER)");
+			current_buffer_stride = 0;
+			current_buffer_offset = 0;
+			current_buffer_size = 0;
+			continue;
+		} else if (d.type == 'I' + 256) {
+			// User wants to draw with an index buffer, consume an argument and see which type the indices have
+			GLuint index_buffer = va_arg(args, GLuint);
+			index_buffer_type = GL_UNSIGNED_INT;
+			size_t index_type_size = sizeof(GLuint);
+			
+			for(char* m = d.modifiers; *m != '\0'; m++) {
+				switch(*m) {
+					case 'b': index_buffer_type = GL_UNSIGNED_BYTE;  index_type_size = sizeof(GLubyte);  break;
+					case 's': index_buffer_type = GL_UNSIGNED_SHORT; index_type_size = sizeof(GLushort); break;
+					default:  fprintf(stderr, "Invalid index buffer directive $%s%c\n", d.modifiers, d.type - 256);
+				}
+			}
+			
+			// Got a vailid type, so bind the index buffer and figure out how many indices are in there
+			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
+			if ( ! gl_error("Unable to bind index buffer. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER)") ) {
+				int index_buffer_size = 0;
+				glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &index_buffer_size);
+				if ( !gl_error("Unable to determine size of index buffer. glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE)") )
+					indices_to_render = index_buffer_size / index_type_size;
+			}
+			
+			use_index_buffer = true;
+			continue;
+		//} else if (d.type == 'F' + 256) {
+		//	// User wants to draw into a framebuffer. Consume an argument and use it as framebuffer.
+		//	use_framebuffer = va_arg(args, GLuint);
+		//	continue;
+		} else if (d.type > 256) {
+			// Got an unknown global option
+			fprintf(stderr, "Unknown global option: $%s%c. Ignoring but consuming one argument.\n", d.modifiers, d.type - 256);
+			va_arg(args, GLuint);
+			continue;
+		}
+		
+		GLint location = 0;
+		if (isupper(d.type)) {
+			// Upper case types are uniforms
+			location = glGetUniformLocation(program, d.name);
+			if ( gl_error("Error on looking up uniform %s. glGetUniformLocation()", d.name) ) {
+				// All glGetUniformLocation errors are caused by invalid programs. So no point in
+				// trying anything else since the program is broken.
+				return -1;
+			} else if (location == -1) {
+				fprintf(stderr, "Program has no uniform \"%s\", ignoring uniform.\n", d.name);
+				continue;
+			}
+		} else {
+			// Lower case types are attributes
+			if (d.name[0] == '_' && d.name[1] == '\0') {
+				// The attribute name "_" is used for padding, so don't try to look it up. Just use it
+				// for offset and stride calculations.
+				location = -1;
+			} else {
+				location = glGetAttribLocation(program, d.name);
+				if ( gl_error("Error on looking up attribute %s. glGetAttribLocation()", d.name) ) {
+					// All glGetAttribLocation errors are caused by invalid programs. So no point in
+					// trying anything else since the program is broken.
+					return -1;
+				} else if (location == -1) {
+					fprintf(stderr, "Program has no attribute \"%s\", attribute unused and it's space will be skipped in the buffer.\n", d.name);
+				}
+			}
+			
+			// We e don't know the stride of the attribute we're going to bind. So sum the size of the
+			// current attribute and all further attributes that use this buffer (so all attributes left
+			// or until we encounter a buffer reset ";").
+			// When we know how large one vertex will be in this buffer we know the stride (vertex size)
+			// and how many vertecies are in the buffer.
+			if (current_buffer_stride == 0) {
+				attribute_info_t ai;
+				if ( parse_attribute_directive(&d, &ai) )
+					current_buffer_stride += ai.type_size * ai.components;
+				
+				directive_t ld;
+				const char* lookahead_bindings = setup_pass_bindings;
+				while( next_directive(&lookahead_bindings, &ld), ld.type && ld.type != ';' ) {
+					// Skip uniforms and global parameters
+					if (ld.type > 256 || isupper(ld.type))
+						continue;
+					
+					if ( parse_attribute_directive(&ld, &ai) )
+						current_buffer_stride += ai.type_size * ai.components;
+				}
+				
+				// Consume an argument, bind it as array buffer and determine the number of vertecies in
+				// there (only needed when we don't use an index buffer for rendering).
+				glBindBuffer(GL_ARRAY_BUFFER, va_arg(args, GLuint));
+				if ( ! gl_error("Unable to bind vertex buffer at attribute %s. glBindBuffer(GL_ARRAY_BUFFER)", d.name) && !use_index_buffer ) {
+					glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &current_buffer_size);
+					// See how many vertecies are in that buffer. In the end we want to render as many
+					// vertecies as are present in all buffers.
+					if ( !gl_error("Unable to determine buffer size at attribute %s. glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE)", d.name) && current_buffer_size > 0 && current_buffer_stride > 0) {
+						uint32_t vertecies_in_buffer = current_buffer_size / current_buffer_stride;
+						if (vertecies_in_buffer < vertecies_to_render)
+							vertecies_to_render = vertecies_in_buffer;
+					}
+					//printf("using buffer: size %d, stride %d, vertecies_to_render: %u\n", current_buffer_size, current_buffer_stride, vertecies_to_render);
+				}
+			}
+		}
+		
+		switch(d.type) {
+			// Uniforms
+			case 'F':
+				if (d.modifier_length != 1) break;
+				switch(d.modifiers[0]) {
+					case '1': glUniform1fv(location, 1, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniform1fv()", d.name); continue;
+					case '2': glUniform2fv(location, 1, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniform2fv()", d.name); continue;
+					case '3': glUniform3fv(location, 1, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniform3fv()", d.name); continue;
+					case '4': glUniform4fv(location, 1, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniform4fv()", d.name); continue;
+				}
+				break;
+			case 'I':
+				if (d.modifier_length != 1) break;
+				switch(d.modifiers[0]) {
+					case '1': glUniform1iv(location, 1, va_arg(args, GLint*)); gl_error("Failed to set uniform %s. glUniform1iv()", d.name); continue;
+					case '2': glUniform2iv(location, 1, va_arg(args, GLint*)); gl_error("Failed to set uniform %s. glUniform2iv()", d.name); continue;
+					case '3': glUniform3iv(location, 1, va_arg(args, GLint*)); gl_error("Failed to set uniform %s. glUniform3iv()", d.name); continue;
+					case '4': glUniform4iv(location, 1, va_arg(args, GLint*)); gl_error("Failed to set uniform %s. glUniform4iv()", d.name); continue;
+				}
+				break;
+			case 'U':
+				if (d.modifier_length != 1) break;
+				switch(d.modifiers[0]) {
+					case '1': glUniform1uiv(location, 1, va_arg(args, GLuint*)); gl_error("Failed to set uniform %s. glUniform1uiv()", d.name); continue;
+					case '2': glUniform2uiv(location, 1, va_arg(args, GLuint*)); gl_error("Failed to set uniform %s. glUniform2uiv()", d.name); continue;
+					case '3': glUniform3uiv(location, 1, va_arg(args, GLuint*)); gl_error("Failed to set uniform %s. glUniform3uiv()", d.name); continue;
+					case '4': glUniform4uiv(location, 1, va_arg(args, GLuint*)); gl_error("Failed to set uniform %s. glUniform4uiv()", d.name); continue;
+				}
+				break;
+			case 'M': {
+				GLboolean transpose = false;
+				
+				// Skip the 3 byte (e.g. "3x2") or 1 byte (e.g. "2") matrix dimensions and loop over all remaining modifiers.
+				// Skip right over everything if we got an invalid modifier so the user gets an error message about it.
+				for(char* m = (d.modifiers[1] == 'x') ? d.modifiers + 3 : d.modifiers + 1; *m != '\0'; m++) {
+					switch(*m) {
+						case 't': transpose = true; break;
+						default: goto invalid_matrix_modifier;
+					}
+				}
+				
+				switch(d.modifiers[0]) {
+					case '2':
+						if (d.modifiers[1] == 'x') {
+							if (d.modifiers[2] == '3')      { glUniformMatrix2x3fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix2x3fv()", d.name); continue; }
+							else if (d.modifiers[2] == '4') { glUniformMatrix2x4fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix2x4fv()", d.name); continue; }
+						} else {
+							glUniformMatrix2fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix2fv()", d.name); continue;
+						}
+						break;
+					case '3':
+						if (d.modifiers[1] == 'x') {
+							if (d.modifiers[2] == '2')      { glUniformMatrix3x2fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix3x2fv()", d.name); continue; }
+							else if (d.modifiers[2] == '4') { glUniformMatrix3x4fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix3x4fv()", d.name); continue; }
+						} else {
+							glUniformMatrix3fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix3fv()", d.name); continue;
+						}
+						break;
+					case '4':
+						if (d.modifiers[1] == 'x') {
+							if (d.modifiers[2] == '2')      { glUniformMatrix4x2fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix4x2fv()", d.name); continue; }
+							else if (d.modifiers[2] == '3') { glUniformMatrix4x3fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix4x3fv()", d.name); continue; }
+						} else {
+							glUniformMatrix4fv(location, 1, transpose, va_arg(args, GLfloat*)); gl_error("Failed to set uniform %s. glUniformMatrix4fv()", d.name); continue;
+						}
+						break;
+				}
+				
+				}
+				invalid_matrix_modifier:
+				break;
+			
+			// Textures, only increment active_textures when the texture can be used successfully. Otherwise
+			// we try to reuse the current texture image unit for the next texture.
+			case 'T': {
+				GLenum target = GL_TEXTURE_2D;
+				
+				for(char* m = d.modifiers; *m != '\0'; m++) {
+					switch(*m) {
+						case 'r': target = GL_TEXTURE_RECTANGLE; break;
+						default: goto invalid_texture_modifier;
+					}
+				}
+				
+				glActiveTexture(GL_TEXTURE0 + active_textures);
+				if ( gl_error("Failed to activate texture image unit %d for texture %s. Probably to many textures. glActiveTexture()", active_textures, d.name) )
+					continue;
+				glBindTexture(target, va_arg(args, GLint));
+				if ( gl_error("Failed to bind texture for %s to %s. glBindTexture()", d.name, (target == GL_TEXTURE_2D) ? "GL_TEXTURE_2D" : "GL_TEXTURE_RECTANGLE") )
+					continue;
+				glUniform1i(location, active_textures);
+				if ( gl_error("Failed to set uniform for texture %s. glUniform1i()", d.name) ) {
+					glBindTexture(target, 0);
+					continue;
+				}
+				
+				active_textures++;
+				continue;
+				
+				}
+				invalid_texture_modifier:
+				break;
+			
+			// Attributes
+			default:
+				// Don't process unknown attributes (would just lead to errors) and ignore padding attributes
+				// TODO: Check if this actually skipps padding attributes in the offset calculation
+				if ( location == -1 )
+					continue;
+				
+				if ( parse_attribute_directive(&d, &ai) ) {
+					// Make sure that we count the attributes size in future offsets. So the buffer layout is the
+					// same even if we fail to use some attributes.
+					size_t offset = current_buffer_offset;
+					current_buffer_offset += ai.type_size * ai.components;
+					
+					glEnableVertexAttribArray(location);
+					if (gl_error("Failed to enable vertex attribute %s. glEnableVertexAttribArray()", d.name))
+						continue;
+					
+					if (ai.upload_as_int)
+						glVertexAttribIPointer(location, ai.components, ai.opengl_type, current_buffer_stride, (GLvoid*)offset);
+					else
+						glVertexAttribPointer(location, ai.components, ai.opengl_type, ai.normalized, current_buffer_stride, (GLvoid*)offset);
+					
+					gl_error("Failed to setup buffer layout for attribute %s. %s()", d.name, ai.upload_as_int ? "glVertexAttribIPointer" : "glVertexAttribPointer");
+					continue;
+				}
+				break;
+		}
+		
+		// We'll only arrive down here if the type is invalid so print out an error message
+		fprintf(stderr, "Invalid type %%%s%c for %s\n", d.modifiers, d.type, d.name);
+	}
+	va_end(args);
+	
+	/*
+	// Check framebuffer binding, and bind the target framebuffer if necessary
+	GLint bound_framebuffer = 0;
+	glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &bound_framebuffer);
+	if ((GLuint)bound_framebuffer != use_framebuffer) {
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, use_framebuffer);
+		// TODO: Figure out how large the frame buffer is and set glViewport accordingly
+	}
+	*/
+	
+	// Draw stuff, start of the fireworks... finally
+	if ( ! use_index_buffer ) {
+		glDrawArrays(primitive, 0, vertecies_to_render);
+		gl_error("Drawcall failed. glDrawArrays()");
+	} else {
+		glDrawElements(primitive, indices_to_render, index_buffer_type, 0);
+		gl_error("Drawcall failed. glDrawElements()");
+	}
+	
+	
+	// Cleanup all texture image units we bound textures to
+	for(size_t i = 0; i < active_textures; i++) {
+		glActiveTexture(GL_TEXTURE0 + i);
+		glBindTexture(GL_TEXTURE_RECTANGLE, 0);
+	}
+	glActiveTexture(GL_TEXTURE0);
+	
+	// Unbind any buffer that has been used by the last attribute
+	if (current_buffer_stride != 0)
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	
+	// Disable all vertex attribute arrays
+	const char* cleanup_bindings = bindings;
+	while( next_directive(&cleanup_bindings, &d) ) {
+		// Skip uniforms, padding attributes and global options
+		if (isupper(d.type) || (d.name[0] == '_' && d.name[1] == '\0') || d.type > 256 )
+			continue;
+		
+		// Skip unknown attributes
+		GLint location = glGetAttribLocation(program, d.name);
+		if (location == -1)
+			continue;
+		
+		glDisableVertexAttribArray(location);
+	}
+	
+	glUseProgram(0);
+	
+	return 0;
+}
+
+/**
+ * Helper function for `draw()`. Parses the next directive in `*bindings`. Returns the type of
+ * the next directive or 0 if there is an error or no next directive. Additional data about the
+ * directive is returned in `output_directive`. `*bindings` is advanced so it points to the end
+ * of the parsed directive.
+ * 
+ * Whitespaces and commas at the beginning of `*bindings` are ignored. In case of an parser error
+ * a message is printed to stderr. To iterate over all directives in a string use:
+ * 
+ *   const char* bindings = ...;
+ *   directive_t directive;
+ *   while( next_directive(&bindings, &directive) ) {
+ *     // ...
+ *   }
+ */
+static int next_directive(const char** bindings, directive_p output_directive) {
+	int consumed_bytes = 0, matched_items = 0;
+	
+	// Remember start for error messages
+	const char** start = bindings;
+	
+	// Reset output values
+	output_directive->name[0] = '\0';
+	output_directive->modifiers[0] = '\0';
+	output_directive->modifier_length = 0;
+	output_directive->type = '\0';
+	
+	// Consume spaces and comma signs
+	while ( *bindings[0] != '\0' && (isspace(*bindings[0]) || *bindings[0] == ',') )
+		*bindings += 1;
+	
+	switch(*bindings[0]) {
+		case ';':
+			// Got a reset buffer directive
+			*bindings += 1;
+			output_directive->type = ';';
+			return output_directive->type;
+		case '%':
+			// Got the type of a uniform or attribute directive. But this is invalid since
+			// we need a name first. Probably an easily made error so we print an extra
+			// error message.
+			fprintf(stderr, "Missing name before uniform or attribute directive \"%s\"\n", *start);
+			return 0;
+		case '$':
+			// Got a global option directive, type is the letter of the type + 256
+			output_directive->type += 256;
+			matched_items = sscanf(*bindings, "$%15s%n", output_directive->modifiers, &consumed_bytes);
+			if (matched_items == EOF)
+				return 0;
+			if (matched_items < 1) {
+				fprintf(stderr, "Failed to parse global option directive \"%s\"\n", *start);
+				return 0;
+			}
+			*bindings += consumed_bytes;
+			break;
+		default:
+			matched_items = sscanf(*bindings, "%127s %%%15s%n", output_directive->name, output_directive->modifiers, &consumed_bytes);
+			if (matched_items == EOF)
+				return 0;
+			if (matched_items < 2 ) {
+				fprintf(stderr, "Failed to parse uniform or attribute directive \"%s\"\n", *start);
+				return 0;
+			}
+			*bindings += consumed_bytes;
+			break;
+	}
+		
+	// Move the last letter of the modifiers to the type field.
+	// Only add the type to output_directive->type since global options already have a 256 offset there. 
+	output_directive->modifier_length = strlen(output_directive->modifiers) - 1;
+	output_directive->type += output_directive->modifiers[output_directive->modifier_length];
+	output_directive->modifiers[output_directive->modifier_length] = '\0';
+	return output_directive->type;
+}
+
+
+/**
+ * Tries to parses `directive` as an attribute directive. Output data like the OpenGL data type,
+ * component count, etc. are stored in `attribute_info`. Returns `true` if the directive is a
+ * attribute directive, `false` otherwise.
+ */
+static bool parse_attribute_directive(directive_p directive, attribute_info_p attribute_info) {
+	attribute_info_p ai = attribute_info;
+	memset(ai, 0, sizeof(attribute_info_t));
+	char* m = directive->modifiers;
+	
+	// Take care of the component count
+	switch(*(m++)) {
+		case '1': ai->components = 1; break;
+		case '2': ai->components = 2; break;
+		case '3': ai->components = 3; break;
+		case '4': ai->components = 4; break;
+		default: return false;
+	}
+	
+	switch(directive->type) {
+		case 'f':
+			ai->opengl_type = GL_FLOAT;
+			ai->type_size = sizeof(GLfloat);
+			
+			for( ; *m != '\0'; m++) {
+				switch(*m) {
+					case 'h': ai->opengl_type = GL_HALF_FLOAT; ai->type_size = sizeof(GLhalf);  break;
+					case 'f': ai->opengl_type = GL_FIXED;      ai->type_size = sizeof(GLfixed); break;
+					default: return false;
+				}
+			}
+			
+			return true;
+			
+		case 'b':
+			ai->opengl_type = GL_BYTE;
+			ai->type_size = sizeof(GLbyte);
+			
+			for( ; *m != '\0'; m++) {
+				switch(*m) {
+					case 'u': ai->opengl_type = GL_UNSIGNED_BYTE; ai->type_size = sizeof(GLubyte); break;
+					case 'n': ai->normalized = true;     break;
+					case 'i': ai->upload_as_int = true;  break;
+					default: return false;
+				}
+			}
+			
+			return true;
+			
+		case 's':
+			ai->opengl_type = GL_SHORT;
+			ai->type_size = sizeof(GLshort);
+			
+			for( ; *m != '\0'; m++) {
+				switch(*m) {
+					case 'u': ai->opengl_type = GL_UNSIGNED_SHORT; ai->type_size = sizeof(GLushort); break;
+					case 'n': ai->normalized = true;     break;
+					case 'i': ai->upload_as_int = true;  break;
+					default: return false;
+				}
+			}
+			
+			return true;
+		
+		case 'i':
+			ai->opengl_type = GL_INT;
+			ai->type_size = sizeof(GLint);
+			
+			for( ; *m != '\0'; m++) {
+				switch(*m) {
+					case 'u': ai->opengl_type = GL_UNSIGNED_INT; ai->type_size = sizeof(GLuint); break;
+					case 'n': ai->normalized = true;     break;
+					case 'i': ai->upload_as_int = true;  break;
+					default: return false;
+				}
+			}
+			
+			return true;
+	}
+	
+	// Unknown or unsupported attribute type
+	return false;
+}
+
+/**
+ * If there is a last OpenGL error this function returns `true` and prints `description` followed
+ * by ": " and the last pending OpenGL error(s) to stderr (a bit like `perror()`). Otherwise `false`
+ * is returned.
+ * 
+ * The `glGetError()` docs say that it might hold multiple pending errors. So before using this function
+ * you should make sure to consume all pending GL errors by looping over `glGetError()` until it returns
+ * `GL_NO_ERROR​`.
+ * 
+ * `gl_error()` behaves like `printf()` so you can use stuff like %s in `directive`
+ * to print additional error information.
+ */
+static bool gl_error(const char* description, ...) {
+	GLenum error = glGetError();
+	if (error == GL_NO_ERROR)
+		return false;
+	
+	va_list args;
+	va_start(args, description);
+	vfprintf(stderr, description, args);
+	va_end(args);
+	
+	const char* gl_error_message = NULL;
+	switch(error) {
+		case GL_INVALID_ENUM:                  gl_error_message = "invalid enum";                  break;
+		case GL_INVALID_VALUE:                 gl_error_message = "invalid value";                 break;
+		case GL_INVALID_OPERATION:             gl_error_message = "invalid operation";             break;
+		case GL_INVALID_FRAMEBUFFER_OPERATION: gl_error_message = "invalid framebuffer operation"; break;
+		case GL_OUT_OF_MEMORY:                 gl_error_message = "out of memory";                 break;
+		case GL_STACK_UNDERFLOW:               gl_error_message = "stack underflow";               break;
+		case GL_STACK_OVERFLOW:                gl_error_message = "stack overflow";                break;
+		default:                               gl_error_message = "unknown OpenGL error";          break;
+	}
+	
+	fprintf(stderr, ": %s\n", gl_error_message);
+	return true;
+}
+
+
+//
+// Utility functions
+//
+
+/**
+ * Checks if an OpenGL extention is avaialbe.
+ */
+static bool gl_ext_present(const char *ext_name){
+	GLint ext_count;
+	glGetIntegerv(GL_NUM_EXTENSIONS, &ext_count);
+	for(ssize_t i = 0; i < ext_count; i++){
+		if ( strcmp((const char*)glGetStringi(GL_EXTENSIONS, i), ext_name) == 0 )
+			return true;
+	}
+	return false;
+}
+
+/**
+ * The library requires some OpenGL extentions. This function checks if these are available. If not
+ * an error message is printed to stderr for each missing extention.
+ * 
+ * Returns whether the requirements are met or not.
+ */
+bool check_required_gl_extentions(){
+	const char* extentions[] = {
+		// Both extentions are required for texture format and handling
+		"GL_ARB_texture_rectangle",
+		"GL_ARB_texture_storage",
+		NULL
+	};
+	
+	bool requirements_met = true;
+	for(size_t i = 0; extentions[i] != NULL; i++){
+		if ( !gl_ext_present(extentions[i]) ) {
+			requirements_met = false;
+			fprintf(stderr, "Required OpenGL extention not available: %s\n", extentions[i]);
+		}
+	}
+	
+	return requirements_met;
+}
+
+
+//
+// Utility functions
+//
+
+/**
+ * Platform indipendent function to read an entire file into memory.
+ * 
+ * Returns a pointer to the zero terminated malloced contents of the file. If size is
+ * not NULL it's target is set to the size of the file not including the zero terminator
+ * at the end of the memory block.
+ * 
+ * On error NULL is returned and errno is set accordingly.
+ */
+void* fload(const char* filename, size_t* size) {
+	long filesize = 0;
+	char* data = NULL;
+	
+	FILE* f = fopen(filename, "rb");
+	if (f == NULL)
+		return NULL;
+	
+	if ( fseek(f, 0, SEEK_END)              == -1       ) goto fail;
+	if ( (filesize = ftell(f))              == -1       ) goto fail;
+	if ( fseek(f, 0, SEEK_SET)              == -1       ) goto fail;
+	if ( (data = malloc(filesize + 1))      == NULL     ) goto fail;
+	if ( (long)fread(data, 1, filesize, f)  != filesize ) goto free_and_fail;
+	fclose(f);
+	
+	data[filesize] = '\0';
+	if (size)
+		*size = filesize;
+	return (void*)data;
+	
+	
+	int error = -1;
+	
+	free_and_fail:
+		error = errno;
+		free(data);
+	
+	fail:
+		if (error == -1)
+			error = errno;
+		fclose(f);
+	
+	errno = error;
+	return NULL;
+}
\ No newline at end of file
diff --git a/slim_gl.h b/slim_gl.h
new file mode 100755
index 0000000..463991b
--- /dev/null
+++ b/slim_gl.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdint.h>
+#define GL_GLEXT_PROTOTYPES
+#include <GL/gl.h>
+
+
+GLuint program_new(const char* vertex_shader_file, const char* fragment_shader_file, FILE* compiler_message_stream);
+GLuint program_new_from_string(const char* vertex_shader_code, const char* fragment_shader_code, FILE* compiler_message_stream);
+void   program_destroy(GLuint program);
+void   program_inspect(GLuint program, FILE* output_stream);
+
+GLuint buffer_new(const void* data, size_t size);
+void   buffer_destroy(GLuint buffer);
+void   buffer_update(GLuint buffer, const void* data, size_t size, GLenum usage);
+
+GLuint texture_new(const void* data, size_t width, size_t height, uint8_t components);
+GLuint texture_new_rect(const void* data, size_t width, size_t height, uint8_t components);
+void   texture_destroy(GLuint texture);
+void   texture_update(GLuint texture, const void* data);
+
+GLuint framebuffer_new(GLuint color_buffer_texture);
+void   framebuffer_destroy(GLuint framebuffer);
+void   framebuffer_blit(GLuint read_framebuffer, GLint rx, GLint ry, GLint rw, GLint rh, GLuint draw_framebuffer, GLint dx, GLint dy, GLint dw, GLint dh);
+void   framebuffer_bind(GLuint framebuffer);
+
+int    render(GLenum primitive, GLuint program, const char* bindings, ...);
+
+#ifdef _SGL_UTILS
+void* fload(const char* filename, size_t* size);
+#endif
\ No newline at end of file