summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatt Turner <mattst88@gmail.com>2012-12-11 12:34:57 -0800
committerMatt Turner <mattst88@gmail.com>2012-12-11 12:34:57 -0800
commit18b7caedb71a56381fafaa724ad3c9fa2dc8e465 (patch)
tree3565888e7a076a5097add2aa935dba9e8937d0a8 /src
Initial import
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am27
-rw-r--r--src/main.cpp691
-rw-r--r--src/shader.cpp69
-rw-r--r--src/shader.h41
4 files changed, 828 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..8d6d249
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,27 @@
+# Copyright © 2012 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 = bezier_surface
+bezier_surface_SOURCES = main.cpp shader.cpp
+
+AM_CXXFLAGS= $(GLU3_CFLAGS)
+bezier_surface_LDADD = $(FRAMEWORK_LIBS) $(GLU3_LIBS) -lEGL
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..34d4959
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,691 @@
+/*
+ * Copyright © 2009, 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 <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;
+
+/* Which data set is begin used for the patches? See set_control_points.
+ */
+static unsigned data_set = 0;
+
+static Uint32 t0;
+
+static GLuint bo;
+
+static GLUmat4 projection_matrix;
+
+class bezier_patch_program : public shader_program {
+public:
+ bezier_patch_program(GLuint vs, GLuint fs)
+ : shader_program(vs, fs, 0)
+ {
+ /* empty */
+ }
+
+ void set_attribute_locations()
+ {
+ }
+
+ void get_uniform_locations()
+ {
+ this->mvp_uniform =
+ glGetUniformLocation(this->program, "mvp");
+ this->mv_normal_uniform =
+ glGetUniformLocation(this->program, "mv_normal");
+ this->light_dir_cs_uniform =
+ glGetUniformLocation(this->program, "light_dir_cs");
+ }
+
+ GLint mvp_uniform;
+ GLint mv_normal_uniform;
+ GLint light_dir_cs_uniform;
+};
+
+static bezier_patch_program *patch_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->uv = &data[0 * num_verts];
+ this->uv_offset = (uintptr_t) ((char *) this->uv
+ - (char *) _data)
+ + base_offset;
+
+ this->position = NULL;
+ this->normal = NULL;
+ this->tangent = NULL;
+ this->vertex_count = num_verts;
+
+ this->elt = (GLushort *) &data[1 * 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 uv_offset;
+
+ GLenum mode;
+};
+
+class patch_producer : public GLUmeshProducer {
+public:
+ patch_producer(unsigned rows, unsigned columns)
+ : GLUmeshProducer(rows, columns, columns)
+ {
+ /* empty */
+ }
+
+ virtual unsigned vertex_count(void) const
+ {
+ return (this->rows + 1) * this->columns;
+ }
+
+ virtual void generate(GLUshapeConsumer *consumer) const
+ {
+ unsigned idx = 0;
+ const float u_step = 1. / (this->columns - 1);
+ const float v_step = 1. / this->rows;
+ float u;
+ float v;
+
+ v = 0;
+ for (unsigned i = 0; i < (this->rows + 1); i++ ) {
+ u = 0.;
+ for (unsigned j = 0; j < this->columns; j++) {
+ consumer->uv[idx] = GLUvec4(u, v, 0., 0.);
+ idx++;
+ u += u_step;
+ }
+
+ v += v_step;
+ }
+
+ consumer->vertex_batch(NULL,
+ NULL,
+ NULL,
+ consumer->uv,
+ idx);
+
+ GLUmeshProducer::generate(consumer);
+ }
+};
+
+static simple_consumer *mesh_sink;
+
+/**
+ * Fill a buffer object with data defining a worm.
+ */
+static simple_consumer *
+fill_in_object_data(GLuint buf)
+{
+ patch_producer mesh_source(29, 29);
+
+ const unsigned num_verts = mesh_source.vertex_count();
+ const unsigned num_elts = mesh_source.element_count();
+
+ const unsigned size = (4 * sizeof(float) * 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);
+ mesh_source.generate(cons);
+
+ glUnmapBuffer(GL_ARRAY_BUFFER);
+
+ return cons;
+}
+
+static GLUvec4
+lerp(GLUvec4 const &a, GLUvec4 const &b, float t)
+{
+ return (b - a) * t + a;
+}
+
+static void
+set_control_points(float angle, GLUvec4 *p)
+{
+ static const GLUvec4 a[16] = {
+ GLUvec4( 9.0f, 0.0f, 7.0f, 1.0f),
+ GLUvec4( 4.5f, 0.0f, 7.0f, 1.0f),
+ GLUvec4(-4.5f, 0.0f, 7.0f, 1.0f),
+ GLUvec4(-9.0f, 0.0f, 7.0f, 1.0f),
+
+ GLUvec4( 9.0f, 0.0f, 3.5f, 1.0f),
+ GLUvec4( 4.5f, 0.0f, 3.5f, 1.0f),
+ GLUvec4(-4.5f, 0.0f, 3.5f, 1.0f),
+ GLUvec4(-9.0f, 0.0f, 3.5f, 1.0f),
+
+ GLUvec4( 9.0f, 0.0f, -3.5f, 1.0f),
+ GLUvec4( 4.5f, 0.0f, -3.5f, 1.0f),
+ GLUvec4(-4.5f, 0.0f, -3.5f, 1.0f),
+ GLUvec4(-9.0f, 0.0f, -3.5f, 1.0f),
+
+ GLUvec4( 9.0f, 0.0f, -7.0f, 1.0f),
+ GLUvec4( 4.5f, 0.0f, -7.0f, 1.0f),
+ GLUvec4(-4.5f, 0.0f, -7.0f, 1.0f),
+ GLUvec4(-9.0f, 0.0f, -7.0f, 1.0f),
+ };
+
+ static const GLUvec4 b[16] = {
+ GLUvec4( 2.0f, 0.0f, 1.0f, 1.0f),
+ GLUvec4( 4.5f, 4.0f, 7.0f, 1.0f),
+ GLUvec4(-4.5f, 4.0f, 7.0f, 1.0f),
+ GLUvec4(-2.0f, 0.0f, 1.0f, 1.0f),
+
+ GLUvec4( 4.0f, 2.0f, 0.5f, 1.0f),
+ GLUvec4( 4.5f, 8.0f, 0.5f, 1.0f),
+ GLUvec4(-4.5f, 8.0f, 0.5f, 1.0f),
+ GLUvec4(-4.0f, 2.0f, 0.5f, 1.0f),
+
+ GLUvec4( 4.0f, 2.0f, -0.5f, 1.0f),
+ GLUvec4( 4.5f, 8.0f, -0.5f, 1.0f),
+ GLUvec4(-4.5f, 8.0f, -0.5f, 1.0f),
+ GLUvec4(-4.0f, 2.0f, -0.5f, 1.0f),
+
+ GLUvec4( 2.0f, 0.0f, -1.0f, 1.0f),
+ GLUvec4( 4.5f, 4.0f, -7.0f, 1.0f),
+ GLUvec4(-4.5f, 4.0f, -7.0f, 1.0f),
+ GLUvec4(-2.0f, 0.0f, -1.0f, 1.0f),
+ };
+
+#define Z (0.866025404 * 10.)
+#define Y (0.5 * 10.)
+
+ static const GLUvec4 c[16] = {
+ GLUvec4( 9.0f, 0.0f, 0.0f, 1.0f),
+ GLUvec4( 4.5f, 0.0f, 0.0f, 1.0f),
+ GLUvec4(-4.5f, 0.0f, 0.0f, 1.0f),
+ GLUvec4(-9.0f, 0.0f, 0.0f, 1.0f),
+
+ GLUvec4( 9.0f, Y, Z, 1.0f),
+ GLUvec4( 4.5f, Y, Z, 1.0f),
+ GLUvec4(-4.5f, Y, Z, 1.0f),
+ GLUvec4(-9.0f, Y, Z, 1.0f),
+
+ GLUvec4( 9.0f, Y, -Z, 1.0f),
+ GLUvec4( 4.5f, Y, -Z, 1.0f),
+ GLUvec4(-4.5f, Y, -Z, 1.0f),
+ GLUvec4(-9.0f, Y, -Z, 1.0f),
+
+ GLUvec4( 9.0f, 0.0f, 0.0f, 1.0f),
+ GLUvec4( 4.5f, 0.0f, 0.0f, 1.0f),
+ GLUvec4(-4.5f, 0.0f, 0.0f, 1.0f),
+ GLUvec4(-9.0f, 0.0f, 0.0f, 1.0f),
+ };
+
+#undef Z
+#undef Y
+
+ const GLUvec4 *const target = (data_set == 0) ? b : c;
+ const float t = 1. - ((cos(angle) + 1.) / 2.);
+
+ for (unsigned i = 0; i < ARRAY_SIZE(a); i++)
+ p[i] = lerp(a[i], target[i], t);
+}
+
+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;
+
+
+ /* For the current time, determine the location of the control points.
+ */
+ GLUvec4 control_points[16];
+ set_control_points(y_angle, control_points);
+
+ const GLUvec4 eye(eye_distance * cos(y_angle),
+ 5.0f,
+ eye_distance * sin(y_angle),
+ 0.0f);
+
+ const GLUmat4 view(gluLookAt(eye,
+ GLUvec4(0.0f, 3.0f, 0.0f, 0.0f),
+ GLUvec4(0.0f, 1.0f, 0.0f, 0.0f)));
+
+ const GLUmat4 mv(view);
+ const GLUmat4 mvp(projection_matrix * mv);
+
+ /* Based on the current time, set the position of the light in
+ * world-space.
+ */
+ const GLUvec4 light_dir_ws(GLUvec4(0.,
+ fabs(cos(y_angle)),
+ sin(y_angle),
+ 0.));
+
+ /* Transform the light position from world-space to camera-space
+ * because all lighting calculations in the shaders will happen in
+ * camera-space.
+ */
+ const GLUvec4 light_dir_cs(view * light_dir_ws);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+
+ /* Render the worm.
+ */
+ glUseProgram(patch_program->program);
+
+ if (timer_queries[0])
+ glBeginQuery(GL_TIME_ELAPSED, timer_queries[0]);
+
+ glUniform3fv(patch_program->light_dir_cs_uniform, 1,
+ (float *) &light_dir_cs);
+ glUniformMatrix4fv(patch_program->mvp_uniform, 1, false,
+ (float *) &mvp);
+
+ /* Since the model-view matrix is an orthonormal basis, we can just
+ * use the upper 3x3 portion for transforming normals.
+ */
+ const float mv_normal[9] = {
+ mv.col[0].values[0], mv.col[0].values[1], mv.col[0].values[2],
+ mv.col[1].values[0], mv.col[1].values[1], mv.col[1].values[2],
+ mv.col[2].values[0], mv.col[2].values[1], mv.col[2].values[2],
+ };
+ glUniformMatrix3fv(patch_program->mv_normal_uniform, 1, false,
+ mv_normal);
+
+ glDrawElements(mesh_sink->mode,
+ mesh_sink->elt_count,
+ GL_UNSIGNED_SHORT,
+ BUFFER_OFFSET(mesh_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 bezier_patch_program *
+create_program(GLuint vs, GLuint fs, const char *name)
+{
+ bezier_patch_program *p = new bezier_patch_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 (patch_program)
+ delete patch_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");
+
+ patch_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_3_0) {
+ printf ("Sorry, this demo requires OpenGL 3.0 or later.\n");
+ exit(1);
+ }
+
+ if (!GLEW_ARB_explicit_attrib_location) {
+ printf ("Sorry, this demo requires "
+ "GL_ARB_explicit_attrib_location.\n");
+ exit(1);
+ }
+
+ if (GLEW_EXT_timer_query || GLEW_ARB_timer_query)
+ glGenQueries(ARRAY_SIZE(timer_queries), timer_queries);
+
+ build_all_shaders();
+
+ glClearColor(.1f, .1f, .1f, 0.f);
+ glEnable(GL_DEPTH_TEST);
+
+ glGenBuffers(1, & bo);
+ mesh_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(mesh_sink->uv_offset));
+ glEnableVertexAttribArray(0);
+
+
+ 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 'd':
+ /* Toggle between the two data sets.
+ */
+ data_set ^= 1;
+ 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 */