From: Stephan Soller Date: Thu, 9 Feb 2017 21:51:46 +0000 (+0100) Subject: Merged Slim GL into a single headerfile library and added documentation. This is... X-Git-Url: https://git.owens.tech///git?a=commitdiff_plain;h=8350bc2e0e2afcf877d00f09e5483677d28ada76;p=forks%2Fsingle-header-file-c-libs.git Merged Slim GL into a single headerfile library and added documentation. This is version 1.0.0. --- diff --git a/slim_gl.c b/slim_gl.c deleted file mode 100755 index e390cdc..0000000 --- a/slim_gl.c +++ /dev/null @@ -1,1265 +0,0 @@ -// For fmemstream() -#define _GNU_SOURCE - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#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 2D or rectangular texture (GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE). - * - * Creates and uploads a 2D 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. If stride is -1 it's assumed to be the - * same as width. - * - * As default a GL_TEXTURE_2D with mipmaps is created and the minifing filter is set to GL_LINEAR_MIPMAP_LINEAR (better - * quality). If data is uploaded the mipmaps are generated as well. You can use the SGL_RECT flag to create a - * GL_TEXTURE_RECTANGLE or the SGL_SKIP_MIPMAPS to skip mipmap generation. - * - * To make the API easier this function only supports textures with 8 bits per pixel (GL_R8, GL_RG8, GL_RGB8 and - * GL_RGBA8). - * - * Flags: - * - * - SGL_RECT: Create a GL_TEXTURE_RECTANGLE. Rectangle textures don't have mipmaps. - * - SGL_SKIP_MIPMAPS: Skip glGenerateMipmap() call after uploading data for a GL_TEXTURE_2D. - * - * Returns the texture object on success or `0` on error. - */ -GLuint texture_new(const void* data, uint32_t width, uint32_t height, uint8_t components, ssize_t stride, uint32_t flags) { - 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; - - GLenum target = 0; - GLsizei mipmap_levels = 1; - GLint prev_bound_texture = 0; - if (flags & SGL_RECT) { - target = GL_TEXTURE_RECTANGLE; - glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &prev_bound_texture); - } else { - target = GL_TEXTURE_2D; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_bound_texture); - - uint32_t w = width, h = height; - while (w > 1 || h > 1) { - mipmap_levels++; - w /= 2; - h /= 2; - } - } - - glBindTexture(target, texture); - glTexStorage2D(target, mipmap_levels, internal_format, width, height); - - // Set high quality texture filtering as default for 2D (not rect) textures - if (target == GL_TEXTURE_2D) - glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - - if (data) { - if (stride == -1) - stride = width; - - GLint prev_unpack_alignment = 0; - glGetIntegerv(GL_UNPACK_ALIGNMENT, &prev_unpack_alignment); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); - - glTexSubImage2D(target, 0, 0, 0, width, height, data_format, GL_UNSIGNED_BYTE, data); - - glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - if ( target == GL_TEXTURE_2D && !(flags & SGL_SKIP_MIPMAPS) ) - glGenerateMipmap(target); - } - glBindTexture(target, prev_bound_texture); - - 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. If stride is -1 it's assumed to be the textures width without padding. - * - * Flags: - * - * - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE. - * - SGL_SKIP_MIPMAPS: Skip calling glGenerateMipmap() after upload. - */ -void texture_update(GLuint texture, const void* data, ssize_t stride, uint32_t flags) { - texture_update_sub(texture, 0, 0, -1, -1, data, stride, flags); -} - -/** - * Uploads new data for to a part of a texture. The data is expected to have the same number of components. If w or h is - * -1 they are assumed set to the textures width and height. If stride is -1 it's assumed to be the same as w. - * - * Flags: - * - * - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE. - * - SGL_SKIP_MIPMAPS: Skip calling glGenerateMipmap() after upload. - */ -void texture_update_sub(GLuint texture, uint32_t x, uint32_t y, int32_t w, int32_t h, const void* data, ssize_t stride, uint32_t flags) { - GLenum target = 0; - GLint prev_bound_texture = 0; - if (flags & SGL_RECT) { - target = GL_TEXTURE_RECTANGLE; - glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &prev_bound_texture); - } else { - target = GL_TEXTURE_2D; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_bound_texture); - } - - glBindTexture(target, texture); - GLint width = w, height = h, internal_format = 0; - if (width == -1) - glGetTexLevelParameteriv(target, 0, GL_TEXTURE_WIDTH, &width); - if (height == -1) - glGetTexLevelParameteriv(target, 0, GL_TEXTURE_HEIGHT, &height); - if (stride == -1) - stride = width; - glGetTexLevelParameteriv(target, 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) { - GLint prev_unpack_alignment = 0; - glGetIntegerv(GL_UNPACK_ALIGNMENT, &prev_unpack_alignment); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); - - glTexSubImage2D(target, 0, x, y, width, height, data_format, GL_UNSIGNED_BYTE, data); - - glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - if ( target == GL_TEXTURE_2D && !(flags & SGL_SKIP_MIPMAPS) ) - glGenerateMipmap(GL_TEXTURE_2D); - } - glBindTexture(target, prev_bound_texture); -} - -/** - * Returns the texutres dimensions in width and height. A width or height of NULL is ignored and safe to use. - * - * Flags: - * - * - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE. - */ -void texture_dimensions(GLuint texture, int32_t* width, int32_t* height, uint32_t flags) { - GLenum target = 0; - GLint prev_bound_texture = 0; - if (flags & SGL_RECT) { - target = GL_TEXTURE_RECTANGLE; - glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &prev_bound_texture); - } else { - target = GL_TEXTURE_2D; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_bound_texture); - } - - glBindTexture(target, texture); - if (width) - glGetTexLevelParameteriv(target, 0, GL_TEXTURE_WIDTH, width); - if (height) - glGetTexLevelParameteriv(target, 0, GL_TEXTURE_HEIGHT, height); - glBindTexture(target, prev_bound_texture); -} - - -// -// Frame buffer functions -// - -GLuint framebuffer_new(GLuint color_buffer_texture, uint32_t flags) { - GLint prev_draw_fb = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw_fb); - - GLenum texture_target = (flags & SGL_RECT) ? GL_TEXTURE_RECTANGLE : GL_TEXTURE_2D; - GLuint framebuffer = 0; - glGenFramebuffers(1, &framebuffer); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target, color_buffer_texture, 0); - gl_error("Failed to bind color buffer to framebuffer. glFramebufferTexture2D()"); - - if ( glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE ) { - glDeleteFramebuffers(1, &framebuffer); - framebuffer = 0; - } - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prev_draw_fb); - 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, GLsizei width, GLsizei height) { - // 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); - if ( !gl_error("Failed to bind framebuffer %u. glBindFramebuffer()", framebuffer) && width != 0 && height != 0 ) - 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: % - * 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 - * * the uniform is a texture array. The argument list must contain a size_t (the array length) and a - * GLuint* (pointer to an array of OpenGL texture names) instead of a single GLuint. - * - * - * 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): - * - * $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; - - // Make sure no previous error code messes up our state - glGetError(); - - 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 > 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 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 global parameters and uniforms - 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, ¤t_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; - size_t array_length = -1; - - for(char* m = d.modifiers; *m != '\0'; m++) { - switch(*m) { - case 'r': target = GL_TEXTURE_RECTANGLE; break; - case '*': array_length = va_arg(args, size_t); break; - default: goto invalid_texture_modifier; - } - } - - if (array_length == (size_t)-1) { - // Just one simple texture - 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++; - } else { - // A texture array (possibly empty but we need to consume the args anyway) - GLuint* textures = va_arg(args, GLuint*); - GLint image_unit_indices[array_length]; - for(size_t i = 0; i < array_length; i++) { - glActiveTexture(GL_TEXTURE0 + active_textures); - if ( gl_error("Failed to activate texture image unit %d for texture array %s. Probably to many textures. glActiveTexture()", active_textures, d.name) ) - break; - image_unit_indices[i] = active_textures; - active_textures++; - - glBindTexture(target, textures[i]); - if ( gl_error("Failed to bind texture for %s[%zu] to %s. glBindTexture()", d.name, i, (target == GL_TEXTURE_2D) ? "GL_TEXTURE_2D" : "GL_TEXTURE_RECTANGLE") ) - continue; - } - - if (array_length > 0) { - glUniform1iv(location, array_length, image_unit_indices); - if ( gl_error("Failed to set uniform for texture array %s. glUniform1iv()", d.name) ) - continue; - } - } - continue; - - } - invalid_texture_modifier: - break; - - // Attributes - default: - 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; - - // Don't process unknown attributes (would just lead to errors) and ignore padding attributes - if ( location == -1 ) - continue; - - 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); - - - // 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; - int error = -1; - - 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; - - 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 old mode 100755 new mode 100644 index 8e82cf0..7cd4476 --- a/slim_gl.h +++ b/slim_gl.h @@ -1,46 +1,1736 @@ -#pragma once +/** + +SlimGL v1.0.0 - Compact OpenGL shorthand functions for common cases + + +Do this: + +#define SLIM_GL_IMPLEMENTATION + +before you include this file in *one* C file to create the implementation. + +// i.e. it should look like this: +#include ... +#include ... +#include ... +#define SLIM_GL_IMPLEMENTATION +#include "slim_gl.h" + + +Core ideas: + +- Single header file library (like stb_image.h). +- A simple API as shortcuts for the most common cases. +- Can be combined with direct OpenGL calls for more complex code (SlimGL functions return native OpenGL object IDs). +- Emphasis on ease of use, not so much on performance. +- Use a printf()/scanf() style API for uniform and attribute setup to make drawcalls more compact and easier to read. + +Right now the API covers the following: + +- OpenGL programs (vertex and fragment shaders): sgl_program_from_files(), sgl_program_from_strings(), sgl_program_destroy() + sgl_program_inspect() and the SGL_GLSL() macro. +- Drawcalls that draw the complete vertex buffer, uniform and vertex attribute setup: sgl_draw(). +- Textures: sgl_texture_new(), sgl_texture_destroy(), sgl_texture_update(), sgl_texture_update_sub() and sgl_texture_dimensions(). +- Framebuffers with just one color attachment: sgl_framebuffer_new(), sgl_framebuffer_destroy() and sgl_framebuffer_bind(). +- Some useful utilities: sgl_error(), sgl_fload() and sgl_strappendf(). + +Example code to render a white triangle on black background: + + // Compile vertex and fragment shaders into an OpenGL program + GLuint program = sgl_program_from_strings(SGL_GLSL("#version 140", + in vec2 pos; + void main() { + gl_Position = vec4(pos, 0, 1); + } + ), SGL_GLSL("#version 140", + void main() { + gl_FragColor = vec4(1); + } + ), NULL); + + // Create a vertex buffer with one triangle in it + struct { float x, y; } vertices[] = { + { 0, 0.5 }, // top + { 0.5, -0.5 }, // right + { -0.5, -0.5 } // left + }; + GLuint buffer = sgl_buffer_new(vertices, sizeof(vertices)); + + // Draw background and triangle + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + sgl_draw(GL_TRIANGLES, program, "pos %2f", buffer); + +For more details please take a look at the documentation of the individual functions. + +Revision history: + + v1.0.0 (2015-09-22) First released version + +License: MIT License + +*/ + + +#ifndef SLIM_GL_HEADER +#define SLIM_GL_HEADER -#include #include -#define GL_GLEXT_PROTOTYPES -#include +// +// OpenGL program functions +// + +/** + * Compiles a vertex and fragment shader from two files, links them into an OpenGL program reports compiler errors on failure. + * + * // Without error reporting + * GLuint object_prog = sgl_program_from_files("object.vs", "object.fs", NULL); + * + * // With error reporting + * char* compiler_errors = NULL; + * GLuint object_prog = sgl_program_from_files("object.vs", "object.fs", &compiler_errors); + * if (!object_prog) { + * printf("Failed to compile object shaders:\n%s", compiler_errors); + * free(compiler_errors); + * compiler_errors = NULL; + * } + * + * On success the OpenGL program ID is returned. On failure `0` is returned and compiler errors are reported. + * If `compiler_errors` is `NULL` the errors are printed to `stderr`. Otherwise it's target is set to a string + * containing the compiler messages. That string has to be `free()`ed by the caller. On error incomplete + * shader and program objects are deleted. + * + * Changed OpenGL state: None. + */ +GLuint sgl_program_from_files(const char* vertex_shader_file, const char* fragment_shader_file, char** compiler_errors); + +/** + * Same as `sgl_program_from_files()` but loads the shader code from strings instead of files. + */ +GLuint sgl_program_from_strings(const char* vertex_shader_code, const char* fragment_shader_code, char** compiler_errors); + +/** + * Destroys the OpenGL program and all shaders attached to it. + * + * Changed OpenGL state: None. + */ +void sgl_program_destroy(GLuint program); + +/** + * Prints all attributes and uniforms of the OpenGL program on stderr. + * + * Changed OpenGL state: None. + */ +void sgl_program_inspect(GLuint program); -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); +/** + * A small macro that can be used to write strings containing GLSL code like normal C. Instead of + * + * "#version 130\n" + * "in vec2 pos;\n" + * "void main() {\n" + * " gl_Position = vec4(pos, 0, 1);\n" + * "}\n" + * + * you can write this + * + * SGL_GLSL("#version 130", + * in vec2 pos; + * void main() { + * gl_Position = vec4(pos, 0, 1); + * } + * ) + * + * All the macro does is to convert the GLSL code to a string and add it to the preprocessor directives + * ("#version 130" in the example above). This way you don't have to take care of the quotes yourself + * and syntax-highlighting works for the GLSL code. For the macro to work properly all commas in the GLSL + * code have to be enclosed in brackets. Otherwise the different parts of the code are interpreted as + * multiple arguments to the macro: + * + * SGL_GLSL("#version 130", + * in vec2 pos, focus; // <-- breaks, use two declarations instead + * void main() { + * vec a, b, c; // <-- breaks, again, use multiple declarations + * gl_Position = vec4(pos, 0, 1); // <-- ok since commas are surrounded by brackets + * } + * ) + * + * GLSL preprocessor directives have to be inside the first string argument or the C preprocessor would + * already interpret them. Note that without a #version directive GLSL version 1.10 is assumed (OpenGL 2.0). + * So you should always define the version you want (e.g. "#version 130" for GLSL 1.30 which is part of + * OpenGL 3.0). + * + * Inspired by the examples of the Chipmunk2D physics library. + */ +#define SGL_GLSL(preproc_directives, code) preproc_directives "\n" #code -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); + +// +// 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. + * + * Changed OpenGL state: If data is uploaded GL_ARRAY_BUFFER binding is reset to 0. + */ +GLuint sgl_buffer_new(const void* data, size_t size); + +/** + * Destroys the buffer object. + * + * Changed OpenGL state: None. + */ +void sgl_buffer_destroy(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, GL_DYNAMIC_COPY. + * + * Changed OpenGL state: GL_ARRAY_BUFFER binding is reset to 0. + */ +void sgl_buffer_update(GLuint buffer, const void* data, size_t size, GLenum usage); + + + +// +// Textures +// #define SGL_RECT (1 << 0) #define SGL_SKIP_MIPMAPS (1 << 1) -GLuint texture_new(const void* data, uint32_t width, uint32_t height, uint8_t components, ssize_t stride, uint32_t flags); -void texture_destroy(GLuint texture); -void texture_update(GLuint texture, const void* data, ssize_t stride, uint32_t flags); -void texture_update_sub(GLuint texture, uint32_t x, uint32_t y, int32_t w, int32_t h, const void* data, ssize_t stride, uint32_t flags); -void texture_dimensions(GLuint texture, int32_t* width, int32_t* height, uint32_t flags); +/** + * Creates and optionally uploads a 2D or rectangle texture 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. If `stride_in_pixels` is `0` the `width` + * is used as stride. + * + * By default a GL_TEXTURE_2D with mipmaps is created and the minifing filter is set to GL_LINEAR_MIPMAP_LINEAR (better + * quality). If data is uploaded the mipmaps are generated as well. You can use SGL_SKIP_MIPMAPS to skip mipmap + * generation (but the levels are still allocated, you have to fill them later on). + * + * To make the API easier this function only supports textures with 8 bits per pixel (GL_R8, GL_RG8, GL_RGB8 and GL_RGBA8). + * + * Flags: + * + * - SGL_RECT: Create a GL_TEXTURE_RECTANGLE instead of a GL_TEXTURE_2D. Rectangle textures don't have mipmaps. + * - SGL_SKIP_MIPMAPS: Skip `glGenerateMipmap()` call after uploading data for a GL_TEXTURE_2D. For example if you want + * to upload many small images into the texture and call `glGenerateMipmap()` after that. + * + * Returns the texture object on success or `0` on error. + * + * Changed OpenGL state: None. + */ +GLuint sgl_texture_new(uint32_t width, uint32_t height, uint8_t components, const void* data, size_t stride_in_pixels, uint32_t flags); + +/** + * Destroys the texture object. + * + * Changed OpenGL state: None. + */ +void sgl_texture_destroy(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 as the texture. If `stride_in_pixels` is `0` the textures width is used as stride. + * + * Flags: + * + * - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE. + * - SGL_SKIP_MIPMAPS: Skip calling `glGenerateMipmap()` after upload. + * + * Changed OpenGL state: None. + */ +void sgl_texture_update(GLuint texture, const void* data, size_t stride_in_pixels, uint32_t flags); + +/** + * Uploads new data for a part of a texture. The data is expected to have the same number of components. If `w` or `h` + * is `0` they are set to the remaining width or height of the texture. If `stride_in_pixels` is `0` it's assumed to be + * the same as `w` (or the remaining width if `w` is `0`). + * + * Flags: + * + * - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE. + * - SGL_SKIP_MIPMAPS: Skip calling `glGenerateMipmap()` after upload. + * + * Changed OpenGL state: None. + */ +void sgl_texture_update_sub(GLuint texture, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const void* data, size_t stride_in_pixels, uint32_t flags); + +/** + * Returns the texutre's dimensions in `width` and `height`. A `width` or `height` of `NULL` is ignored and safe to use. + * + * Flags: + * + * - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE. + * + * Changed OpenGL state: None. + * Temporarily changed and restored OpenGL state: GL_TEXTURE_BINDING_2D or GL_TEXTURE_BINDING_RECTANGLE (if the SGL_RECT flag is set). + * + * Changed OpenGL state: None. + */ +void sgl_texture_dimensions(GLuint texture, uint32_t* width, uint32_t* height, uint32_t flags); + + + +// +// Frame buffer functions +// + +/** + * Creates a framebuffer with `color_buffer_texture` as its color butter attachtment. + * + * Flags: + * + * - SGL_RECT: `color_buffer_texture` is a GL_TEXTURE_RECTANGLE instead of a GL_TEXTURE_2D. + * + * Returns the framebuffer object on success or `0` on error. + * + * Changed OpenGL state: None. + */ +GLuint sgl_framebuffer_new(GLuint color_buffer_texture, uint32_t flags); + +/** + * Destroys the framebuffer object. + * + * Changed OpenGL state: None. + */ +void sgl_framebuffer_destroy(GLuint framebuffer); + +/** + * Binds the specified framebuffer and sets the viewport dimensions to `width` and `height`. + * + * Changed OpenGL state: GL_DRAW_FRAMEBUFFER_BINDING. + */ +void sgl_framebuffer_bind(GLuint framebuffer, GLsizei width, GLsizei height); + + + +// +// Draw functions +// + +/** + * Draws `primitive`s with a specified OpenGL `program` using glDrawElements(). If an index buffer is + * specified glDrawArrays() is used (see ... below). Uniforms and vertex data is setup according to + * directives in the `bindings` string (similar to `printf()` directives). + * + * The `bindings` string is a compact notation for the glGetUniformLocation, glUniform, glGetAttribLocation, + * glBindBuffer, glVertexAttribPointer, glBindTexture, etc. calls. For each directive `sgl_draw` calls + * these functions to setup a uniform, an attribute or other state for the draw call (like an index buffer). + * + * + * # Specifying vertex data + * + * Vertex data is specified with `printf()` style directives. The function call + * + * sgl_draw(GL_TRIANGLES, program, "pos %3f color %3f", vertex_buffer); + * + * tells OpenGL that the "pos" and "color" attributes are three component float vectors and the data should be + * fetched from the buffer `vertex_buffer`. You only have to specify an OpenGL buffer object for the first + * attribute directive, all other attributes fetch their data from the same buffer. + * + * Each directive consists of a name and a format specifier, e.g. "pos" and "%3f". For each one the function + * + * - queries the location of the attribute with glGetAttribLocation(), + * - enables it with glEnableVertexAttribArray(), + * - and tells OpenGL the format of the attribute with glVertexAttribPointer(). + * + * The stride and size for each attribute are calculated automatically under the assumption that the attributes + * are tightly packed in the vertex buffer. You can use the special attribute name "_" to specify padding. For + * example "_ %4f" will skip the space of a 4 component float vector in the buffer. + * + * + * ## Vertex format specifier + * + * Each specifier is in the form: "%" + * + * A format specifier is nothing more than a compact way to write the glVertexAttribPointer() arguments. + * Please take a look at the documentation of glVertexAttribPointer() if you ask yourself what the dimensions, + * flags and type mean. + * + * Possible dimensions: "1", "2", "3", "4" + * The dimension is mandatory. Even if you just have one float you need to write "%1f". + * + * Possible types: + * + * "f" (GL_FLOAT) + * "b" (GL_BYTE) + * "s" (GL_SHORT) + * "i" (GL_INT) + * + * Possible flags for type "f" (GL_FLOAT): + * + * "h" Use GL_HALF_FLOAT instead of GL_FLOAT + * "f" Use GL_FIXED instead of GL_FLOAT + * + * Possible flags for type "b" (GL_BYTE): + * + * "u" Use GL_UNSIGNED_BYTE instead of GL_BYTE + * "n" Values are normalized. The entire value range of the data type is mapped to 0..1 in the shader. + * "i" Upload as int (use glVertexAttribIPointer()) + * + * Possible flags for type "s" (GL_SHORT): + * + * "u" Use GL_UNSIGNED_SHORT instead of GL_SHORT + * "n" Values are normalized. The entire value range of the data type is mapped to 0..1 in the shader. + * "i" Upload as int (use glVertexAttribIPointer()) + * + * Possible flags for type "i" (GL_INT): + * + * "u" Use GL_UNSIGNED_INT instead of GL_INT + * "n" Values are normalized. The entire value range of the data type is mapped to 0..1 in the shader. + * "i" Upload as int (use glVertexAttribIPointer()) + * + * For example the format specifier "%4unb" tells glVertexAttribPointer() to unpack 4 unsigned and + * normalized bytes into a 4 component vector in the shader. The 4 values {128, 255, 64, 255} in the + * vertex buffer will be mapped to vec4(0.5, 1.0, 0.25, 1.0) in the shader. By using this format specifier + * you can store colors in the vertex buffer in a very compact way. + * + * + * # Specifying uniforms + * + * Uniforms are specified similarly to attributes but they use upper case types and different flags. Each + * uniform also consumes one argument of the sgl_draw() function. + * + * float projection[16] = { ... }; + * struct{ float x, y, z; } light = { ... }; + * sgl_draw(GL_TRIANGLES, program, "projection %4tM light_pos %3F pos %3f color %3f", projection, &light, vertex_buffer); + * + * This call sets the uniform "projection" to the values of the "projection" array. The uniform is a 4x4 + * matrix (type "M" with 4 dimensions). A float[16] on the CPU and a mat4 in the shader. The matrix is + * transposed when uploaded ("t" flag). The second uniform "light_pos" is set to a 3 component float vector + * (type "F" with 3 dimensions). + * + * Each uniform directive consumes one argument of the `sgl_draw()` function. The arguments are consumed + * in the same order as the directives are listed in the `bindings` string. The arguments for uniforms have + * to be _pointers_ to the data for the uniform (sgl_draw() always uses the glUniform*v() functions). In the + * above example the variable "projection" is already a pointer to the first byte of the float[16] array. + * But the variable "light" is a struct so you need to get the pointer to it's location in memory. This is + * the same even for a single float: + * + * float threshold = ...; + * sgl_draw(GL_TRIANGLES, program, "threshold %1F pos %3f color %3f", &threshold, vertex_buffer); + * + * You can freely mix uniform and attribute directives in any order. But keep in mind that only the first + * vertex attribute directive (lower case type) consumes an argument. + * + * + * ## Uniform format specifier + * + * Each specifier is in the form: "%" + * + * A format specifier is nothing more than a compact way to choose one of the glUniform*v() functions and set + * some of its flags. Please take a look at the documentation of glUniform*v() if you ask yourself what the + * dimensions, flags and type mean. + * + * Possible dimensions: "1", "2", "3", "4" + * The dimension is mandatory. Even if you just have one float you need to write "%1F". + * For matrix uniforms you can also use: "2x3", "2x4", "3x2", "3x4", "4x2", "4x3" + * + * Possible types: + * + * "F" (float, glUniform*fv) + * "I" (integer, glUniform*iv) + * "U" (unsigned integer, glUniform*uiv) + * "M" (matrix, glUniformMatrix*fv) + * + * Possible flags for type "F" (float): None. + * Possible flags for type "I" (integer): None. + * Possible flags for type "U" (unsigned integer): None. + * Possible flags for type "M" (matrix): + * + * "t" Transpose matrix. + * + * + * # Specifying textures + * + * Textures are specified as uniforms with the type "T". It binds a GL_TEXTURE_2D texture to a sampler uniform. + * The matching argument has to be the object ID of the texture (GLuint), not a pointer. The function automatically + * calls glActiveTexture(), glBindTexture() and glUniform1i(). + * + * Possible flags for type "T" (texture): + * + * "r" Texture is a GL_TEXTURE_RECTANGLE texture instead of a GL_TEXTURE_2D texture + * "*" The uniform is a texture array. The argument list must contain a size_t (the array length) and a + * GLuint* (pointer to an array of OpenGL texture names) instead of a single GLuint. + * + * + * # Global options + * + * Global options start with "$" instead of "%". Right now there is just one: + * + * $I Draw with an index buffer of GL_UNSIGNED_INT indices. One argument of type GLuint is consumed. + * + * Possible flags: + * + * "b" Indices are of type GL_UNSIGNED_BYTE + * "s" Indices are of type GL_UNSIGNED_SHORT + * + * + * # Using data from multiple vertex buffers + * + * The first attribute directive 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 ";" then consumes a new buffer argument. Stride and offsets of attributes are calculated + * automatically. + * + * Example: + * + * sgl_draw(GL_TRIANGLES, program, "pos %3f normal %3f ; color %4unb", geometry_vertex_buffer, color_vertex_buffer); + * + * This draw call takes the data for the "pos" and "normal" attributes from the "geometry_vertex_buffer" + * and the "color" attribute is read from the "color_vertex_buffer". Each buffer contains its data tighly + * packed. + * + * + * # TODO + * + * - Change unsigned integer uniforms from %1U to %1uI (as "u" flag for "I" type instead of an extra type). + * Should be more consistent with attribute format specifier (where it's "%1ui"). + * - Check if cleanup of texture binding of really necessary. If so make sure we unbind the proper target. + * Basically we have to iterate over the directives again to get the target of each directive. + * + * + * # Reference + * + * Form of directives: "%" + * 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 glUniform1uiv + * %2U glUniform2uiv + * %3U glUniform3uiv + * %4U glUniform4uiv + * M (matrix) + * %2M glUniformMatrix2fv + * %2x3M glUniformMatrix2x3fv + * %2x4M glUniformMatrix2x4fv + * %3M glUniformMatrix3fv + * %3x2M glUniformMatrix3x2fv + * %3x4M glUniformMatrix3x4fv + * %4M glUniformMatrix4fv + * %4x2M glUniformMatrix4x2fv + * %4x3M glUniformMatrix4x3fv + * + * Flags: + * t transpose matrix + * + * T (textures) + * %T bind a GL_TEXTURE_2D texture to a sampler uniform + * + * Flags: + * r texture is a GL_TEXTURE_RECTANGLE texture + * * the uniform is a texture array. The argument list must contain a size_t (the array length) and a + * GLuint* (pointer to an array of OpenGL texture names) instead of a single GLuint. + * + * + * 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 + * + */ +int sgl_draw(GLenum primitive, GLuint program, const char* bindings, ...); + + +// +// Utilities +// + +/** + * Returns the last OpenGL error and outputs an error message to stderr (a bit like `perror()`). If no + * error happened `0` is returned and nothing is printed. To make the code more readable `sgl_error()` + * is meant to be used in the `if` that checks for errors: + * + * location = glGetUniformLocation(program, name); + * if ( sgl_error("Failed to lookup uniform %s. glGetUniformLocation()", name) ) { + * // react to error + * } + * + * If there is a last OpenGL error this function prints `description` followed by ": " and the last + * pending OpenGL error to stderr. It behaves like `printf()` so you can use stuff like %s in `description` + * to print additional error information. + */ +GLenum sgl_error(const char* description, ...); -GLuint framebuffer_new(GLuint color_buffer_texture, uint32_t flags); -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, GLsizei width, GLsizei height); +/** + * Returns a pointer to the zero terminated `malloc()`ed 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* sgl_fload(const char* filename, size_t* size); -int render(GLenum primitive, GLuint program, const char* bindings, ...); +/** + * Appends a printf style string to an already existing string pointed to by `dest`. The + * existing string is realloced to be large enough. If `dest` is `NULL` a new string is + * allocated. Returns the result string or `NULL` on error. + * + * char* a = NULL; + * sgl_strappendf(&a, "Hello %s!\n", "World"); + * sgl_strappendf(&a, "x: %f y: %f\n", 7.0, 13.7); + * + * char* b = sgl_strappendf(NULL, "%d %s\n", 42, "is the answer"); + * + */ +char* sgl_strappendf(char** dest, const char* format, ...); -#ifdef _SGL_UTILS -void* fload(const char* filename, size_t* size); +#endif // SLIM_GL_HEADER + + + + +#ifdef SLIM_GL_IMPLEMENTATION + +#include +#include +#include +#include +#include + +#include +#include +#include + + +// +// OpenGL program functions +// + +static const char* sgl__type_to_string(GLenum type); +static GLuint sgl__create_and_compile_program(const char* vertex_shader_code, const char* fragment_shader_code, const char* vertex_shader_name, const char* fragment_shader_name, char** compiler_errors); +static GLuint sgl__create_and_compile_shader(GLenum shader_type, const char* code, const char* filename_for_errors, char** compiler_errors); + +GLuint sgl_program_from_files(const char* vertex_shader_file, const char* fragment_shader_file, char** compiler_errors) { + char* vertex_shader_code = sgl_fload(vertex_shader_file, NULL); + if (vertex_shader_code == NULL) { + if (compiler_errors) + *compiler_errors = sgl_strappendf(NULL, "Can't read vertex shader file %s: %s\n", vertex_shader_file, strerror(errno)); + return 0; + } + + char* fragment_shader_code = sgl_fload(fragment_shader_file, NULL); + if (fragment_shader_code == NULL) { + free(vertex_shader_code); + if (compiler_errors) + *compiler_errors = sgl_strappendf(NULL, "Can't read fragment shader file %s: %s\n", fragment_shader_file, strerror(errno)); + return 0; + } + + GLuint program = sgl__create_and_compile_program(vertex_shader_code, fragment_shader_code, vertex_shader_file, fragment_shader_file, compiler_errors); + + free(vertex_shader_code); + free(fragment_shader_code); + + return program; +} + +GLuint sgl_program_from_strings(const char* vertex_shader_code, const char* fragment_shader_code, char** compiler_errors) { + return sgl__create_and_compile_program(vertex_shader_code, fragment_shader_code, "vertex shader", "fragment shader", compiler_errors); +} + +void sgl_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]); +} + +void sgl_program_inspect(GLuint program) { + GLint size; + GLenum type; + { + GLint attrib_count = 0, buffer_size = 0; + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attrib_count); + fprintf(stderr, "%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(stderr, "- %s %s", buffer, sgl__type_to_string(type)); + if (size > 1) + fprintf(stderr, "[%d]", size); + fprintf(stderr, "\n"); + } + } + + { + GLint uniform_count = 0, buffer_size = 0; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count); + fprintf(stderr, "%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(stderr, "- %s %s", buffer, sgl__type_to_string(type)); + if (size > 1) + fprintf(stderr, "[%d]", size); + fprintf(stderr, "\n"); + } + } +} + +/** + * Helper function to return the printable name for an OpenGL data type. + */ +static const char* sgl__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_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_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_BUFFER: return "usamplerBuffer"; + case GL_UNSIGNED_INT_SAMPLER_2D_RECT: return "usampler2DRect"; + +# ifdef GL_VERSION_3_2 + case GL_SAMPLER_2D_MULTISAMPLE: return "sampler2DMS"; + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return "sampler2DMSArray"; + case GL_INT_SAMPLER_2D_MULTISAMPLE: return "isampler2DMS"; + case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "isampler2DMSArray"; + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return "usampler2DMS"; + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "usampler2DMSArray"; +# endif + + default: return "unknown"; + } +} + +/** + * Helper function to compile and link a complete program. + * + * Returns the program object ID or `0` on error. Compiler errors are returned in `compiler_errors` if it's not `NULL`. + */ +static GLuint sgl__create_and_compile_program(const char* vertex_shader_code, const char* fragment_shader_code, const char* vertex_shader_name, const char* fragment_shader_name, char** compiler_errors) { + char* errors = NULL; + + GLuint vertex_shader = sgl__create_and_compile_shader(GL_VERTEX_SHADER, vertex_shader_code, vertex_shader_name, &errors); + GLuint fragment_shader = sgl__create_and_compile_shader(GL_FRAGMENT_SHADER, fragment_shader_code, fragment_shader_name, &errors); + 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){ + result = 0; + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &result); + char buffer[result]; + glGetProgramInfoLog(prog, result, NULL, buffer); + sgl_strappendf(&errors, "Can't link vertex and pixel shader:\n%s\n", buffer); + + 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); + + if (*errors) { + if (compiler_errors) { + *compiler_errors = errors; + } else { + fprintf(stderr, "%s", errors); + free(errors); + } + } + + return 0; +} + +/** + * Helper function to load and compile source code as a shader. + * + * Returns the shaders object ID on success or `0` on error. Compiler errors in the shader are appended to the + * `compiler_errors` string (if it's not `NULL`). + */ +static GLuint sgl__create_and_compile_shader(GLenum shader_type, const char* code, const char* filename_for_errors, char** compiler_errors) { + GLuint shader = glCreateShader(shader_type); + glShaderSource(shader, 1, (const char*[]){ code }, (const int[]){ -1 }); + glCompileShader(shader); + + GLint result = GL_TRUE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &result); + if (result) + return shader; + + if (compiler_errors) { + result = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &result); + char buffer[result]; + glGetShaderInfoLog(shader, result, NULL, buffer); + sgl_strappendf(compiler_errors, "Can't compile %s:\n%s\n", filename_for_errors, buffer); + } + + glDeleteShader(shader); + return 0; +} + + + +// +// Buffer functions +// + +GLuint sgl_buffer_new(const void* data, size_t size) { + GLuint buffer = 0; + glGenBuffers(1, &buffer); + if (buffer == 0) + return 0; + + if (size > 0) + sgl_buffer_update(buffer, data, size, GL_STATIC_DRAW); + + return buffer; +} + +void sgl_buffer_destroy(GLuint buffer) { + glDeleteBuffers(1, (const GLuint[]){ buffer }); +} + +void sgl_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 +// + +GLuint sgl_texture_new(uint32_t width, uint32_t height, uint8_t components, const void* data, size_t stride_in_pixels, uint32_t flags) { + 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; + + GLenum target = 0; + GLsizei mipmap_levels = 1; + GLint prev_bound_texture = 0; + if (flags & SGL_RECT) { + target = GL_TEXTURE_RECTANGLE; + glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &prev_bound_texture); + } else { + target = GL_TEXTURE_2D; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_bound_texture); + + // We just look for the highest bit set. Then we know how often we can half the width or hight. + mipmap_levels = 32 - __builtin_clz(width | height); + } + + glBindTexture(target, texture); + // Version with ARB_texture_storage + //glTexStorage2D(target, mipmap_levels, internal_format, width, height); + // Replacement taken out of the glTexStorage2D() docs + GLsizei w = width, h = height; + for (GLsizei i = 0; i < mipmap_levels; i++) { + glTexImage2D(target, i, internal_format, w, h, 0, data_format, GL_UNSIGNED_BYTE, NULL); + w /= 2; + h /= 2; + if (w < 1) w = 1; + if (h < 1) h = 1; + } + + // Set high quality texture filtering as default for 2D (not rect) textures + if (target == GL_TEXTURE_2D) + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + if (data) { + if (stride_in_pixels == 0) + stride_in_pixels = width; + + GLint prev_unpack_alignment = 0, prev_row_length = 0; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &prev_unpack_alignment); + glGetIntegerv(GL_UNPACK_ROW_LENGTH, &prev_row_length); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ROW_LENGTH, stride_in_pixels); + + glTexSubImage2D(target, 0, 0, 0, width, height, data_format, GL_UNSIGNED_BYTE, data); + + glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); + glPixelStorei(GL_UNPACK_ROW_LENGTH, prev_row_length); + + if ( target == GL_TEXTURE_2D && !(flags & SGL_SKIP_MIPMAPS) ) + glGenerateMipmap(target); + } + glBindTexture(target, prev_bound_texture); + + return texture; +} + +void sgl_texture_destroy(GLuint texture) { + glDeleteTextures(1, (const GLuint[]){ texture }); +} + +void sgl_texture_update(GLuint texture, const void* data, size_t stride_in_pixels, uint32_t flags) { + sgl_texture_update_sub(texture, 0, 0, 0, 0, data, stride_in_pixels, flags); +} + +void sgl_texture_update_sub(GLuint texture, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const void* data, size_t stride_in_pixels, uint32_t flags) { + GLenum target = 0; + GLint prev_bound_texture = 0; + if (flags & SGL_RECT) { + target = GL_TEXTURE_RECTANGLE; + glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &prev_bound_texture); + } else { + target = GL_TEXTURE_2D; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_bound_texture); + } + + glBindTexture(target, texture); + GLint width = w, height = h, internal_format = 0; + if (width == 0) { + glGetTexLevelParameteriv(target, 0, GL_TEXTURE_WIDTH, &width); + width -= x; + } + if (height == 0) { + glGetTexLevelParameteriv(target, 0, GL_TEXTURE_HEIGHT, &height); + height -= y; + } + if (stride_in_pixels == 0) { + stride_in_pixels = width; + } + glGetTexLevelParameteriv(target, 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) { + GLint prev_unpack_alignment = 0, prev_row_length = 0; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &prev_unpack_alignment); + glGetIntegerv(GL_UNPACK_ROW_LENGTH, &prev_row_length); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ROW_LENGTH, stride_in_pixels); + + glTexSubImage2D(target, 0, x, y, width, height, data_format, GL_UNSIGNED_BYTE, data); + + glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); + glPixelStorei(GL_UNPACK_ROW_LENGTH, prev_row_length); + + if ( target == GL_TEXTURE_2D && !(flags & SGL_SKIP_MIPMAPS) ) + glGenerateMipmap(GL_TEXTURE_2D); + } + glBindTexture(target, prev_bound_texture); +} + +void sgl_texture_dimensions(GLuint texture, uint32_t* width, uint32_t* height, uint32_t flags) { + GLenum target = 0; + GLint prev_bound_texture = 0; + if (flags & SGL_RECT) { + target = GL_TEXTURE_RECTANGLE; + glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &prev_bound_texture); + } else { + target = GL_TEXTURE_2D; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_bound_texture); + } + + glBindTexture(target, texture); + // TODO: error checking + if (width) + glGetTexLevelParameteriv(target, 0, GL_TEXTURE_WIDTH, (int32_t*)width); + if (height) + glGetTexLevelParameteriv(target, 0, GL_TEXTURE_HEIGHT, (int32_t*)height); + glBindTexture(target, prev_bound_texture); +} + + +// +// Frame buffer functions +// + +GLuint sgl_framebuffer_new(GLuint color_buffer_texture, uint32_t flags) { + GLint prev_draw_fb = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw_fb); + + GLenum texture_target = (flags & SGL_RECT) ? GL_TEXTURE_RECTANGLE : GL_TEXTURE_2D; + GLuint framebuffer = 0; + glGenFramebuffers(1, &framebuffer); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target, color_buffer_texture, 0); + sgl_error("Failed to bind color buffer to framebuffer. glFramebufferTexture2D()"); + + if ( glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE ) { + glDeleteFramebuffers(1, &framebuffer); + framebuffer = 0; + } + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prev_draw_fb); + return framebuffer; +} + +void sgl_framebuffer_destroy(GLuint framebuffer) { + glDeleteFramebuffers(1, &framebuffer); +} + +void sgl_framebuffer_bind(GLuint framebuffer, GLsizei width, GLsizei height) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); + if ( !sgl_error("Failed to bind framebuffer %u. glBindFramebuffer()", framebuffer) && width != 0 && height != 0 ) + 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); + +int sgl_draw(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; + + // Make sure no previous error code messes up our state + glGetError(); + + 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); + sgl_error("Failed to generate and bind a new vertex array object. glBindVertexArray()"); + } + + glUseProgram(program); + if ( sgl_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); + sgl_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 ( ! sgl_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 ( !sgl_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 > 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 ( sgl_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 ( sgl_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 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 global parameters and uniforms + 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 ( ! sgl_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, ¤t_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 ( !sgl_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*)); sgl_error("Failed to set uniform %s. glUniform1fv()", d.name); continue; + case '2': glUniform2fv(location, 1, va_arg(args, GLfloat*)); sgl_error("Failed to set uniform %s. glUniform2fv()", d.name); continue; + case '3': glUniform3fv(location, 1, va_arg(args, GLfloat*)); sgl_error("Failed to set uniform %s. glUniform3fv()", d.name); continue; + case '4': glUniform4fv(location, 1, va_arg(args, GLfloat*)); sgl_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*)); sgl_error("Failed to set uniform %s. glUniform1iv()", d.name); continue; + case '2': glUniform2iv(location, 1, va_arg(args, GLint*)); sgl_error("Failed to set uniform %s. glUniform2iv()", d.name); continue; + case '3': glUniform3iv(location, 1, va_arg(args, GLint*)); sgl_error("Failed to set uniform %s. glUniform3iv()", d.name); continue; + case '4': glUniform4iv(location, 1, va_arg(args, GLint*)); sgl_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*)); sgl_error("Failed to set uniform %s. glUniform1uiv()", d.name); continue; + case '2': glUniform2uiv(location, 1, va_arg(args, GLuint*)); sgl_error("Failed to set uniform %s. glUniform2uiv()", d.name); continue; + case '3': glUniform3uiv(location, 1, va_arg(args, GLuint*)); sgl_error("Failed to set uniform %s. glUniform3uiv()", d.name); continue; + case '4': glUniform4uiv(location, 1, va_arg(args, GLuint*)); sgl_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*)); sgl_error("Failed to set uniform %s. glUniformMatrix2x3fv()", d.name); continue; } + else if (d.modifiers[2] == '4') { glUniformMatrix2x4fv(location, 1, transpose, va_arg(args, GLfloat*)); sgl_error("Failed to set uniform %s. glUniformMatrix2x4fv()", d.name); continue; } + } else { + glUniformMatrix2fv(location, 1, transpose, va_arg(args, GLfloat*)); sgl_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*)); sgl_error("Failed to set uniform %s. glUniformMatrix3x2fv()", d.name); continue; } + else if (d.modifiers[2] == '4') { glUniformMatrix3x4fv(location, 1, transpose, va_arg(args, GLfloat*)); sgl_error("Failed to set uniform %s. glUniformMatrix3x4fv()", d.name); continue; } + } else { + glUniformMatrix3fv(location, 1, transpose, va_arg(args, GLfloat*)); sgl_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*)); sgl_error("Failed to set uniform %s. glUniformMatrix4x2fv()", d.name); continue; } + else if (d.modifiers[2] == '3') { glUniformMatrix4x3fv(location, 1, transpose, va_arg(args, GLfloat*)); sgl_error("Failed to set uniform %s. glUniformMatrix4x3fv()", d.name); continue; } + } else { + glUniformMatrix4fv(location, 1, transpose, va_arg(args, GLfloat*)); sgl_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; + size_t array_length = -1; + + for(char* m = d.modifiers; *m != '\0'; m++) { + switch(*m) { + case 'r': target = GL_TEXTURE_RECTANGLE; break; + case '*': array_length = va_arg(args, size_t); break; + default: goto invalid_texture_modifier; + } + } + + if (array_length == (size_t)-1) { + // Just one simple texture + glActiveTexture(GL_TEXTURE0 + active_textures); + if ( sgl_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 ( sgl_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 ( sgl_error("Failed to set uniform for texture %s. glUniform1i()", d.name) ) { + glBindTexture(target, 0); + continue; + } + + active_textures++; + } else { + // A texture array (possibly empty but we need to consume the args anyway) + GLuint* textures = va_arg(args, GLuint*); + GLint image_unit_indices[array_length]; + for(size_t i = 0; i < array_length; i++) { + glActiveTexture(GL_TEXTURE0 + active_textures); + if ( sgl_error("Failed to activate texture image unit %d for texture array %s. Probably to many textures. glActiveTexture()", active_textures, d.name) ) + break; + image_unit_indices[i] = active_textures; + active_textures++; + + glBindTexture(target, textures[i]); + if ( sgl_error("Failed to bind texture for %s[%zu] to %s. glBindTexture()", d.name, i, (target == GL_TEXTURE_2D) ? "GL_TEXTURE_2D" : "GL_TEXTURE_RECTANGLE") ) + continue; + } + + if (array_length > 0) { + glUniform1iv(location, array_length, image_unit_indices); + if ( sgl_error("Failed to set uniform for texture array %s. glUniform1iv()", d.name) ) + continue; + } + } + continue; + + } + invalid_texture_modifier: + break; + + // Attributes + default: + 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; + + // Don't process unknown attributes (would just lead to errors) and ignore padding attributes + if ( location == -1 ) + continue; + + glEnableVertexAttribArray(location); + if (sgl_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); + + sgl_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); + + + // Draw stuff, start of the fireworks... finally + if ( ! use_index_buffer ) { + glDrawArrays(primitive, 0, vertecies_to_render); + sgl_error("Drawcall failed. glDrawArrays()"); + } else { + glDrawElements(primitive, indices_to_render, index_buffer_type, 0); + sgl_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 `sgl_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; +#ifdef GL_VERSION_4_1 + case 'f': ai->opengl_type = GL_FIXED; ai->type_size = sizeof(GLfixed); break; #endif + 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; +} + + +// +// Utility functions +// -/* +GLenum sgl_error(const char* description, ...) { + GLenum error = glGetError(); + if (error == GL_NO_ERROR) + return GL_NO_ERROR; + + 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; +} -Ideas: +void* sgl_fload(const char* filename, size_t* size) { + long filesize = 0; + char* data = NULL; + int error = -1; + + 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; + // TODO: proper error detection for fread and get proper error code with ferror + if ( (long)fread(data, 1, filesize, f) != filesize ) goto free_and_fail; + fclose(f); + + data[filesize] = '\0'; + if (size) + *size = filesize; + return (void*)data; + + free_and_fail: + error = errno; + free(data); + + fail: + if (error == -1) + error = errno; + fclose(f); + + errno = error; + return NULL; +} -- add cube maps -- SGL_DEPTH, SGL_STENCIL flags for textures -> as backend for FBOs +char* sgl_strappendf(char** dest, const char* format, ...) { + va_list args; + int append_size = 0; + + va_start(args, format); + append_size = vsnprintf(NULL, 0, format, args); + va_end(args); + + if (append_size == -1) + return NULL; + + size_t old_size = 0; + char* str = NULL; + if (dest && *dest) { + old_size = strlen(*dest); + str = *dest; + } else { + old_size = 0; + str = NULL; + } + + size_t size = old_size + append_size + 1; + str = realloc(str, size); + va_start(args, format); + vsnprintf(str + old_size, size - old_size, format, args); + va_end(args); + + if (dest) + *dest = str; + return str; +} -*/ \ No newline at end of file +#endif // SLIM_GL_IMPLEMENTATION \ No newline at end of file