summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatt Turner <mattst88@gmail.com>2012-12-11 11:05:52 -0800
committerMatt Turner <mattst88@gmail.com>2012-12-11 11:05:52 -0800
commitde28b8ba484266a03b8650326b306785d75720b3 (patch)
tree2113476378d3799659bd3d7ee57335630d0810f5 /src
Initial import
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am27
-rw-r--r--src/main.cpp647
-rw-r--r--src/shader.cpp69
-rw-r--r--src/shader.h41
4 files changed, 784 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..838519b
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,27 @@
+# Copyright © 2009 Ian D. Romanick
+# All Rights Reserved.
+#
+# 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
+# on the rights to use, copy, modify, merge, publish, distribute, sub
+# license, 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 (including the next
+# paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL
+# AUTHORS, COPYRIGHT HOLDERS, AND/OR THEIR SUPPLIERS 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.
+
+bin_PROGRAMS = worm
+worm_SOURCES = main.cpp shader.cpp
+
+AM_CXXFLAGS= $(GLU3_CFLAGS)
+worm_LDADD = $(FRAMEWORK_LIBS) $(GLU3_LIBS) -lEGL
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..78050f3
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,647 @@
+/*
+ * Copyright © 2009 Ian D. Romanick
+ *
+ * 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 (including the next
+ * paragraph) 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.
+ */
+#include <math.h>
+#include <GL/glew.h>
+#include <SDL.h>
+
+#include <glu3.h>
+
+#include "shader.h"
+
+#define BUFFER_OFFSET(i) ((char *)NULL + (i))
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+static SDL_Surface *my_surf = NULL;
+
+static bool anim = true;
+static bool done = false;
+
+static float y_angle = 0.0f;
+static float eye_distance = 15.0f;
+static float fov = 45.0f;
+
+static Uint32 t0;
+
+static GLuint bo;
+
+static GLUmat4 projection_matrix;
+
+class complex_points_program : public shader_program {
+public:
+ complex_points_program(GLuint vs, GLuint fs)
+ : shader_program(vs, fs, 0)
+ {
+ /* empty */
+ }
+
+ void set_attribute_locations()
+ {
+ glBindAttribLocation(this->program, 0, "position");
+ glBindAttribLocation(this->program, 1, "vert_color");
+ glBindAttribLocation(this->program, 2, "weight");
+ }
+
+ void get_uniform_locations()
+ {
+ this->vp_uniform =
+ glGetUniformLocation(this->program, "vp");
+ this->m_uniform =
+ glGetUniformLocation(this->program, "m");
+ }
+
+ GLint vp_uniform;
+ GLint m_uniform;
+ GLint weight_uniform;
+};
+
+static complex_points_program *worm_program = 0;
+
+static GLuint timer_queries[1] = { 0, };
+
+static inline
+float degrees_to_radians(float f)
+{
+ return (f * M_PI) / 180.0;
+}
+
+
+class simple_consumer : public GLUshapeConsumer {
+public:
+ simple_consumer(void *_data, unsigned num_verts, uintptr_t base_offset)
+ {
+ GLUvec4 *data = (GLUvec4 *) _data;
+
+ this->position = &data[0 * num_verts];
+ this->position_offset = (uintptr_t) ((char *) this->position
+ - (char *) _data)
+ + base_offset;
+
+ this->normal = &data[1 * num_verts];
+ this->normal_offset = (uintptr_t) ((char *) this->normal
+ - (char *) _data)
+ + base_offset;
+
+ this->uv = &data[2 * num_verts];
+ this->uv_offset = (uintptr_t) ((char *) this->uv
+ - (char *) _data)
+ + base_offset;
+
+ this->tangent = NULL;
+ this->vertex_count = num_verts;
+
+ this->elt = (GLushort *) &data[3 * num_verts];
+ this->elt_offset = (uintptr_t) ((char *) this->elt
+ - (char *) _data)
+ + base_offset;
+ }
+
+ virtual void begin_primitive(GLenum mode, unsigned count)
+ {
+ this->mode = mode;
+ this->elt_count = count;
+ }
+
+ virtual void index_batch(const unsigned *idx, unsigned count)
+ {
+ for (unsigned i = 0; i < count; i++)
+ this->elt[i] = GLushort(idx[i]);
+
+ this->elt += count;
+ }
+
+ GLushort *elt;
+ uintptr_t elt_offset;
+ unsigned elt_count;
+
+ uintptr_t position_offset;
+ uintptr_t normal_offset;
+ uintptr_t uv_offset;
+
+ GLenum mode;
+};
+
+static inline float frac(float x)
+{
+ return x - floor(x);
+}
+
+class worm_producer : public GLUmeshProducer {
+public:
+ worm_producer(float _length, unsigned _bones)
+ : GLUmeshProducer(5, 9, 9), length(_length), bones(_bones)
+ {
+ /* empty */
+ }
+
+ virtual unsigned
+ vertex_count(void) const
+ {
+ return (this->rows + 1) * this->columns;
+ }
+
+ virtual void
+ generate(GLUshapeConsumer *consumer) const
+ {
+ float data[2 * 8];
+
+ for (unsigned i = 0; i < 8; i++) {
+ const float angle = float(i) * ((2.0 * M_PI) / 8.0);
+ data[(2 * i) + 0] = cos(angle);
+ data[(2 * i) + 1] = sin(angle);
+ }
+
+ const float step = this->length / float(this->rows);
+ unsigned idx = 0;
+ for (unsigned i = 0; i < (this->rows + 1); i++) {
+ for (unsigned j = 0; j < 8; j++) {
+ consumer->position[idx] =
+ GLUvec4(2.0f * data[(2 * j) + 0],
+ step * float(i),
+ 2.0f * data[(2 * j) + 1],
+ 1.0f);
+ consumer->normal[idx] =
+ GLUvec4(data[(2 * j) + 0],
+ 0.0f,
+ data[(2 * j) + 1],
+ 0.0f);
+ consumer->uv[idx] = encode_weight(i);
+
+ idx++;
+ }
+
+ consumer->position[idx] =
+ GLUvec4(2.0f * data[(2 * 0) + 0],
+ step * float(i),
+ 2.0f * data[(2 * 0) + 1],
+ 1.0f);
+ consumer->normal[idx] =
+ GLUvec4(data[0],
+ 0.0f,
+ data[1],
+ 0.0f);
+ consumer->uv[idx] = encode_weight(i);
+ idx++;
+ }
+
+ consumer->vertex_batch(consumer->position,
+ consumer->normal,
+ consumer->tangent,
+ consumer->uv,
+ idx);
+
+ GLUmeshProducer::generate(consumer);
+ }
+
+private:
+ GLUvec4 encode_weight(unsigned i) const
+ {
+ const unsigned n = (this->bones - 1) * i;
+ const unsigned d = (this->rows);
+ const float w = float(n) / float(d);
+ const unsigned b = n / d;
+
+ return GLUvec4(float(b),
+ float(b + 1),
+ 1.0 - frac(w),
+ 0.0f);
+ }
+
+ float length;
+ unsigned bones;
+};
+
+
+static simple_consumer *worm_sink;
+
+/**
+ * Fill a buffer object with data defining a worm.
+ */
+static simple_consumer *
+fill_in_object_data(GLuint buf)
+{
+ worm_producer worm_source(10.0, 6);
+
+ const unsigned num_verts = worm_source.vertex_count();
+ const unsigned num_elts = worm_source.element_count();
+
+ const unsigned size = (3 * sizeof(GLUvec4) * num_verts)
+ + (sizeof(GLushort) * num_elts);
+
+ glBindBuffer(GL_ARRAY_BUFFER, buf);
+ glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_STATIC_DRAW);
+ void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
+
+ simple_consumer *cons = new simple_consumer(ptr, num_verts, 0);
+ worm_source.generate(cons);
+
+ glUnmapBuffer(GL_ARRAY_BUFFER);
+
+ return cons;
+}
+
+
+static void
+Redisplay(void)
+{
+ static Uint32 last_t = ~0;
+ Uint32 t = SDL_GetTicks();
+
+
+ /* Update animation parameters based on the elaped time.
+ */
+ if (last_t != (Uint32)~0) {
+ float dt = (float) (t - last_t) / 1000.0f;
+
+ if (anim) {
+ /* Rotate at 60 degrees per second respectively. All
+ * of the measurements want angles measured in
+ * radians. Do the conversion here.
+ */
+ y_angle += degrees_to_radians(60.0 * dt);
+ }
+ }
+
+ last_t = t;
+
+ /* Storage for all of the transformation matrices.
+ */
+ GLUmat4 transformations[6];
+
+ const GLUvec4 eye(eye_distance * cos(0.0),
+ 5.0f,
+ eye_distance * sin(0.0),
+ 0.0f);
+
+ const GLUmat4 vp(projection_matrix
+ * gluLookAt(eye,
+ GLUvec4(-5.0f, 3.0f, 0.0f, 0.0f),
+ GLUvec4(0.0f, 1.0f, 0.0f, 0.0f)));
+
+ /* Each of the bones rotates the segment by an angle such that the
+ * combined rotation is pi when the full rotation is used. Time the
+ * animation using y_angle to oscillate between -pi and pi with no
+ * rotation initially.
+ */
+ const float h = sin(y_angle);
+ const double angle = (h * M_PI) / float(ARRAY_SIZE(transformations) - 2);
+
+ GLUmat4 model(gluIdentityMatrix);
+ transformations[0] = model;
+
+ for (unsigned i = 1; i < (ARRAY_SIZE(transformations) - 1); i++) {
+ /* FINISHME: Generate forward-kinematic transformations for
+ * FINISHME: each of the bones.
+ */
+ transformations[i] = model;
+ }
+
+ transformations[5] = transformations[4];
+
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+
+ /* Render the worm.
+ */
+ glUseProgram(worm_program->program);
+
+ if (timer_queries[0])
+ glBeginQuery(GL_TIME_ELAPSED, timer_queries[0]);
+
+ glUniformMatrix4fv(worm_program->vp_uniform, 1, false, (float *) &vp);
+
+ glUniformMatrix4fv(worm_program->m_uniform, ARRAY_SIZE(transformations),
+ false, (float *) &transformations[0]);
+
+ glDrawElements(worm_sink->mode,
+ worm_sink->elt_count,
+ GL_UNSIGNED_SHORT,
+ BUFFER_OFFSET(worm_sink->elt_offset));
+
+ if (timer_queries[0])
+ glEndQuery(GL_TIME_ELAPSED);
+
+ SDL_GL_SwapBuffers();
+
+ if (timer_queries[0]) {
+ static GLuint64 totals[ARRAY_SIZE(timer_queries)] = { 0, };
+ static unsigned count = 0;
+ GLuint64 result[ARRAY_SIZE(timer_queries)] = { 0, };
+
+ for (unsigned i = 0; i < ARRAY_SIZE(timer_queries); i++) {
+ glGetQueryObjectui64vEXT(timer_queries[i],
+ GL_QUERY_RESULT,
+ &result[i]);
+ totals[i] += result[i];
+ }
+
+ count++;
+ if (count > 100) {
+ printf("Timings: %lu\n",
+ totals[0] / 100);
+
+ memset(totals, 0, sizeof(totals));
+ count = 0;
+ }
+ }
+
+}
+
+static GLuint
+compile_shader_from_file(GLenum target, const char *filename)
+{
+ static const char *const paths_to_try[] = {
+ "",
+ "data/",
+ "../data/",
+ 0
+ };
+
+ GLuint shader;
+ char *log = NULL;
+ const char *code;
+
+ const size_t name_len = strlen(filename);
+ for (unsigned i = 0; paths_to_try[i] != 0; i++) {
+ const size_t path_len = strlen(paths_to_try[i]);
+ char *name_with_path = new char [path_len + name_len + 1];
+
+ memcpy(name_with_path, paths_to_try[i], path_len);
+ memcpy(&name_with_path[path_len], filename, name_len);
+ name_with_path[path_len + name_len] = '\0';
+
+ code = gluLoadTextFile(name_with_path);
+ delete [] name_with_path;
+
+ if (code != NULL)
+ break;
+ }
+
+ if (code == NULL) {
+ fprintf(stderr, "Unable to load shader code for %s.\n",
+ filename);
+ exit(1);
+ }
+
+ shader = gluCompileShader(target, code, &log);
+ if (log != NULL) {
+ printf("Shader compile log for %s:\n%s\n", filename, log);
+ gluReleaseInfoLog(log);
+ log = NULL;
+ }
+
+ if (shader == 0) {
+ fprintf(stderr, "Failed to compile %s.\n", filename);
+ exit(1);
+ }
+
+ gluUnloadTextFile(code);
+
+ return shader;
+}
+
+static complex_points_program *
+create_program(GLuint vs, GLuint fs, const char *name)
+{
+ complex_points_program *p = new complex_points_program(vs, fs);
+
+ p->set_attribute_locations();
+
+ const bool linked = p->link();
+ if (p->log != NULL)
+ printf("%s link log:\n%s\n", name, p->log);
+
+ if (!linked) {
+ fprintf(stderr, "Failed to link %s.\n", name);
+ exit(1);
+ }
+
+ p->get_uniform_locations();
+ return p;
+}
+
+/* Load, compile, link, etc. the vertex and fragment programs.
+ */
+static void
+build_all_shaders(void)
+{
+ gluInitializeCompiler();
+
+ /* If there are dangling instances of these program hanging around,
+ * clean them up.
+ */
+ if (worm_program)
+ delete worm_program;
+
+ /* Compile all of the shaders
+ */
+ GLuint vs = compile_shader_from_file(GL_VERTEX_SHADER,
+ "simple.vert");
+ GLuint fs = compile_shader_from_file(GL_FRAGMENT_SHADER,
+ "simple.frag");
+
+ worm_program = create_program(vs, fs, "simple");
+
+ /* The individual shaders have been attached to the programs, so they
+ * can safely be deleted.
+ */
+ glDeleteShader(vs);
+ glDeleteShader(fs);
+}
+
+
+static void
+Init(void)
+{
+ glewInit();
+
+ if (!GLEW_VERSION_2_0) {
+ printf ("Sorry, this demo requires OpenGL 2.0 or later.\n");
+ exit(1);
+ }
+
+ if (GLEW_EXT_timer_query || GLEW_ARB_timer_query)
+ glGenQueries(ARRAY_SIZE(timer_queries), timer_queries);
+
+ build_all_shaders();
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+
+
+ glGenBuffers(1, & bo);
+ worm_sink = fill_in_object_data(bo);
+
+ glBindBuffer(GL_ARRAY_BUFFER, bo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo);
+
+ glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0,
+ BUFFER_OFFSET(worm_sink->position_offset));
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0,
+ BUFFER_OFFSET(worm_sink->normal_offset));
+ glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0,
+ BUFFER_OFFSET(worm_sink->uv_offset));
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glEnableVertexAttribArray(2);
+
+
+ printf("GL_RENDERER = %s\n", (const char *) glGetString(GL_RENDERER));
+ printf("Keyboard input:\n"
+ " f: Toggle fullscreen.\n"
+ " a: Toggle animation of object.\n"
+ " c: Re-load and compile shader program code.\n"
+ " ESC: Exit program.\n");
+}
+
+
+static void
+Reshape(int width, int height)
+{
+ my_surf = SDL_SetVideoMode(width, height, 0,
+ SDL_OPENGL | SDL_RESIZABLE);
+ if (my_surf == NULL) {
+ exit(1);
+ }
+
+ Init();
+
+ const float aspect = float(width) / float(height);
+ gluPerspective4f(& projection_matrix, degrees_to_radians(fov),
+ aspect, 2.0f, 100.0f);
+ glViewport(0, 0, width, height);
+}
+
+
+static void
+Key(SDLKey sym, bool key_down)
+{
+ if (!key_down)
+ return;
+
+ switch (sym) {
+ case SDLK_ESCAPE:
+ done = true;
+ break;
+
+ case 'f':
+ SDL_WM_ToggleFullScreen(my_surf);
+ break;
+
+
+ case ' ':
+ case 'a':
+ anim = !anim;
+ break;
+
+ case 'c':
+ printf("Reloading and compiling program code...\n");
+ build_all_shaders();
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/**
+ * Push an event each time the timer callback fires.
+ */
+Uint32
+timer_callback(Uint32 interval, void *not_used)
+{
+ SDL_Event e;
+
+ (void) not_used;
+
+ e.type = SDL_USEREVENT;
+ e.user.code = 0;
+ e.user.data1 = NULL;
+ e.user.data2 = NULL;
+ SDL_PushEvent(& e);
+
+ return interval;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ (void) argc;
+ (void) argv;
+
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
+ exit(1);
+ }
+
+ atexit(SDL_Quit);
+
+ Reshape(800, 600);
+
+ SDL_TimerID timer_id = SDL_AddTimer(10, timer_callback, NULL);
+ t0 = SDL_GetTicks();
+
+ while (!done) {
+ bool draw = false;
+ SDL_Event event;
+
+ SDL_WaitEvent(&event);
+ do {
+ switch (event.type) {
+ case SDL_VIDEORESIZE:
+ Reshape(event.resize.w, event.resize.h);
+ break;
+
+ case SDL_QUIT:
+ done = true;
+ break;
+
+ case SDL_KEYDOWN:
+ Key(event.key.keysym.sym, true);
+ break;
+
+ case SDL_KEYUP:
+ Key(event.key.keysym.sym, false);
+ break;
+
+ case SDL_USEREVENT:
+ draw = true;
+ break;
+
+ default:
+ break;
+ }
+ } while (SDL_PollEvent(&event));
+
+
+ if (draw) {
+ Redisplay();
+ }
+ }
+
+ SDL_RemoveTimer(timer_id);
+
+ SDL_Quit();
+ return 0;
+}
diff --git a/src/shader.cpp b/src/shader.cpp
new file mode 100644
index 0000000..8456362
--- /dev/null
+++ b/src/shader.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2012 Ian D. Romanick
+ *
+ * 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 (including the next
+ * paragraph) 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.
+ */
+#include <stdarg.h>
+#include <GL/glew.h>
+#include <glu3.h>
+#include "shader.h"
+
+void shader_program::init()
+{
+ this->program = glCreateProgram();
+ this->log = 0;
+}
+
+shader_program::shader_program()
+{
+ init();
+}
+
+shader_program::shader_program(GLint shader, ...)
+{
+ va_list ap;
+ GLint sh;
+
+ init();
+
+ glAttachShader(this->program, shader);
+
+ va_start(ap, shader);
+ while ((sh = va_arg(ap, GLint)) != 0) {
+ glAttachShader(this->program, sh);
+ }
+ va_end(ap);
+}
+
+shader_program::~shader_program()
+{
+ glDeleteProgram(this->program);
+ this->program = 0;
+
+ if (this->log) {
+ gluReleaseInfoLog(this->log);
+ this->log = 0;
+ }
+}
+
+bool shader_program::link()
+{
+ return gluLinkProgram(this->program, &this->log);
+}
diff --git a/src/shader.h b/src/shader.h
new file mode 100644
index 0000000..5e49dc3
--- /dev/null
+++ b/src/shader.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2012 Ian D. Romanick
+ *
+ * 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 (including the next
+ * paragraph) 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.
+ */
+#ifndef SHADER_PROGRAM_H
+#define SHADER_PROGRAM_H
+
+class shader_program {
+public:
+ shader_program();
+ shader_program(GLint shader, ...);
+ ~shader_program();
+
+ bool link();
+
+ GLint program;
+ char *log;
+
+private:
+ void init();
+};
+
+#endif /* SHADER_PROGRAM_H */