summaryrefslogtreecommitdiff
path: root/src/shl_gl_shader.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shl_gl_shader.c')
-rw-r--r--src/shl_gl_shader.c286
1 files changed, 286 insertions, 0 deletions
diff --git a/src/shl_gl_shader.c b/src/shl_gl_shader.c
new file mode 100644
index 0000000..093d786
--- /dev/null
+++ b/src/shl_gl_shader.c
@@ -0,0 +1,286 @@
+/*
+ * shl - OpenGL Helpers
+ *
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Shader API
+ * This provides basic shader objects that are used to draw sprites and
+ * textures.
+ */
+
+#define GL_GLEXT_PROTOTYPES
+
+#include <errno.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include "shl_gl.h"
+#include "shl_llog.h"
+
+#define LLOG_SUBSYSTEM "gl_shader"
+
+struct gl_shader {
+ unsigned long ref;
+ llog_submit_t llog;
+ void *llog_data;
+
+ GLuint program;
+ GLuint vshader;
+ GLuint fshader;
+};
+
+/* Clear the GL error stack. The standard says that the error value is just a
+ * single value and no list/stack. However, multiple error fields may be defined
+ * and glGetError() returns only one of them until all are cleared. Hence, we
+ * loop until no more errors are retrieved. */
+void gl_clear_error()
+{
+ GLenum err;
+
+ do {
+ err = glGetError();
+ } while (err != GL_NO_ERROR);
+}
+
+const char *gl_err_to_str(GLenum err)
+{
+ switch (err) {
+ case GL_NO_ERROR:
+ return "<NO_ERROR>";
+ case GL_INVALID_ENUM:
+ return "<INVALID_ENUM>";
+ case GL_INVALID_VALUE:
+ return "<INVALID_VALUE>";
+ case GL_INVALID_OPERATION:
+ return "<INVALID_OPERATION>";
+#ifdef GL_STACK_OVERFLOW
+ case GL_STACK_OVERFLOW:
+ return "<STACK_OVERFLOW>";
+#endif
+#ifdef GL_STACK_UNDERFLOW
+ case GL_STACK_UNDERFLOW:
+ return "<STACK_UNDERFLOW>";
+#endif
+ case GL_OUT_OF_MEMORY:
+ return "<OUT_OF_MEMORY>";
+ default:
+ return "<unknown>";
+ }
+}
+
+/* return true if there is a pending GL error */
+bool gl_has_error(struct gl_shader *shader)
+{
+ GLenum err;
+
+ err = glGetError();
+ if (err != GL_NO_ERROR) {
+ llog_error(shader, "GL error %d: %s", err, gl_err_to_str(err));
+ return true;
+ }
+
+ return false;
+}
+
+static int compile_shader(struct gl_shader *shader, GLenum type,
+ const char *source)
+{
+ char msg[512];
+ GLint status = 1;
+ GLuint s;
+
+ s = glCreateShader(type);
+ if (s == GL_NONE) {
+ llog_warning(shader, "cannot allocate GL shader");
+ return GL_NONE;
+ }
+
+ glShaderSource(s, 1, &source, NULL);
+ glCompileShader(s);
+
+ glGetShaderiv(s, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE) {
+ msg[0] = 0;
+ glGetShaderInfoLog(s, sizeof(msg), NULL, msg);
+ llog_warning(shader, "cannot compile shader: %s", msg);
+ return GL_NONE;
+ }
+
+ return s;
+}
+
+int gl_shader_new(struct gl_shader **out, const char *vert, const char *frag,
+ char **attr, size_t attr_count, llog_submit_t llog,
+ void *llog_data)
+{
+ struct gl_shader *shader;
+ int ret, i;
+ char msg[512];
+ GLint status = 1;
+
+ if (!out || !vert || !frag)
+ return -EINVAL;
+
+ shader = malloc(sizeof(*shader));
+ if (!shader)
+ return -ENOMEM;
+ memset(shader, 0, sizeof(*shader));
+ shader->ref = 1;
+ shader->llog = llog;
+ shader->llog_data = llog_data;
+
+ llog_debug(shader, "new shader");
+
+ shader->vshader = compile_shader(shader, GL_VERTEX_SHADER, vert);
+ if (shader->vshader == GL_NONE) {
+ ret = -EFAULT;
+ goto err_free;
+ }
+
+ shader->fshader = compile_shader(shader, GL_FRAGMENT_SHADER, frag);
+ if (shader->fshader == GL_NONE) {
+ ret = -EFAULT;
+ goto err_vshader;
+ }
+
+ shader->program = glCreateProgram();
+ glAttachShader(shader->program, shader->vshader);
+ glAttachShader(shader->program, shader->fshader);
+
+ for (i = 0; i < attr_count; ++i)
+ glBindAttribLocation(shader->program, i, attr[i]);
+
+ glLinkProgram(shader->program);
+ glGetProgramiv(shader->program, GL_LINK_STATUS, &status);
+ if (status == GL_FALSE) {
+ msg[0] = 0;
+ glGetProgramInfoLog(shader->program, sizeof(msg), NULL, msg);
+ llog_warning(shader, "cannot link shader: %s", msg);
+ ret = -EFAULT;
+ goto err_link;
+ }
+
+ if (gl_has_error(shader)) {
+ llog_warning(shader, "shader creation failed");
+ ret = -EFAULT;
+ goto err_link;
+ }
+
+ *out = shader;
+ return 0;
+
+err_link:
+ glDeleteProgram(shader->program);
+ glDeleteShader(shader->fshader);
+err_vshader:
+ glDeleteShader(shader->vshader);
+err_free:
+ free(shader);
+ return ret;
+}
+
+void gl_shader_ref(struct gl_shader *shader)
+{
+ if (!shader || !shader->ref)
+ return;
+
+ ++shader->ref;
+}
+
+void gl_shader_unref(struct gl_shader *shader)
+{
+ if (!shader || !shader->ref || --shader->ref)
+ return;
+
+ llog_debug(shader, "free shader");
+
+ glDeleteProgram(shader->program);
+ glDeleteShader(shader->fshader);
+ glDeleteShader(shader->vshader);
+ free(shader);
+}
+
+GLuint gl_shader_get_uniform(struct gl_shader *shader, const char *name)
+{
+ if (!shader)
+ return 0;
+
+ return glGetUniformLocation(shader->program, name);
+}
+
+void gl_shader_use(struct gl_shader *shader)
+{
+ if (!shader)
+ return;
+
+ glUseProgram(shader->program);
+}
+
+void gl_tex_new(GLuint *tex, size_t num)
+{
+ size_t i;
+
+ glGenTextures(num, tex);
+
+ for (i = 0; i < num; ++i) {
+ glBindTexture(GL_TEXTURE_2D, tex[i]);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+ GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
+ GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
+ GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
+ GL_CLAMP_TO_EDGE);
+ }
+}
+
+void gl_tex_free(GLuint *tex, size_t num)
+{
+ glDeleteTextures(num, tex);
+}
+
+void gl_tex_load(GLuint tex, unsigned int width, unsigned int stride,
+ unsigned int height, uint8_t *buf)
+{
+ if (!buf || !width || !height || !stride)
+ return;
+
+ /* With OpenGL instead of OpenGLES2 we must use this on linux:
+ * glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA,
+ * GL_UNSIGNED_BYTE, buf);
+ *
+ * TODO: Check what kind of stride we need to support here.
+ * GL_UNPACK_ROW_LENGTH only supports specifying a single row but
+ * doesn't allow pixel strides. cairo currently works fine without
+ * touching it but we should probably fix this properly. */
+
+ glBindTexture(GL_TEXTURE_2D, tex);
+ /* glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); */
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, width, height, 0,
+ GL_BGRA_EXT, GL_UNSIGNED_BYTE, buf);
+ /* glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); */
+}