summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Turner <mattst88@gmail.com>2012-10-17 21:18:11 -0700
committerMatt Turner <mattst88@gmail.com>2012-10-17 21:18:11 -0700
commitdd8fd986e62e001a7119245a8e6de3e545121635 (patch)
tree24e79aea509921b5447e66a0e7cbcc4ac8e6c7b7
Initial import
-rw-r--r--.gitignore16
-rw-r--r--Makefile.am30
-rw-r--r--configure.ac98
-rw-r--r--data/simple.frag6
-rw-r--r--data/simple.vert11
-rw-r--r--src/Makefile.am27
-rw-r--r--src/main.cpp529
-rw-r--r--src/shader.cpp69
-rw-r--r--src/shader.h41
9 files changed, 827 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..098075e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache/
+config.h
+config.h.in~
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+.deps/
+src/complex-plane
+stamp-h1
+*.o
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..36f264e
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,30 @@
+# 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.
+
+AUTOMAKE_OPTIONS = foreign
+SUBDIRS = src
+
+EXTRA_DIST = \
+ data/simple.vert \
+ data/simple.frag \
+ rotating_cubes.sln \
+ src/shader.h
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..0c0f432
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,98 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.61)
+AC_INIT(rotating_cubes, 2009Q1.1, idr@freedesktop.org, rotating_cubes)
+AC_CONFIG_SRCDIR([Makefile.am])
+AM_CONFIG_HEADER([config.h])
+
+AM_INIT_AUTOMAKE
+
+AM_MAINTAINER_MODE
+
+# Checks for programs.
+AC_PROG_CXX
+AC_PROG_CC
+AC_PROG_MAKE_SET
+AC_PROG_RANLIB
+PKG_PROG_PKG_CONFIG
+
+# Checks for libraries.
+
+# Checks for header files.
+
+# Checks for typedefs, structures, and compiler characteristics.
+
+# Checks for library functions.
+AC_HEADER_STDC
+
+
+# SDL is required for this application. Find its header files and
+# libraries. Make sure they exist.
+
+CPPFLAGS="$CPPFLAGS `sdl-config --cflags`"
+LIBS="$LIBS `sdl-config --libs`"
+
+AC_CHECK_HEADER(SDL.h, [], [exit 1])
+
+CPPFLAGS="$CPPFLAGS `pkg-config --cflags alsa`"
+LIBS="$LIBS `pkg-config --libs alsa`"
+
+# OpenGL is required for this application. Find its header files and
+# libraries. Make sure they exist.
+
+AC_CHECK_HEADER(GL/gl.h, [], [exit 1])
+AC_CHECK_LIB([GL], [glVertexPointer], [], [exit 1])
+
+
+# GLEW is required for this application. Find its header files and
+# libraries. Make sure they exist.
+
+AC_CHECK_HEADER(GL/glew.h, [], [exit 1])
+AC_CHECK_LIB([GLEW], [glewInit], [], [exit 1], [-lGL])
+
+AC_LANG_CPLUSPLUS
+
+PKG_CHECK_MODULES([GLU3], [glu3])
+AC_SUBST(GLU3_CFLAGS)
+AC_SUBST(GLU3_LIBS)
+
+AC_ARG_ENABLE([debug],
+ [AS_HELP_STRING([--enable-debug],
+ [use debug compiler flags and macros @<:@default=disabled@:>@])],
+ [enable_debug="$enableval"],
+ [enable_debug=no]
+)
+if test "x$enable_debug" = xyes; then
+ DEFINES="$DEFINES -DDEBUG"
+ if test "x$GCC" = xyes; then
+ # Remove any -g flags from the command line
+ CFLAGS=[`echo $CFLAGS | sed 's/-g[^ \t]*[ \t]*//g'`]
+ CFLAGS="$CFLAGS -ggdb3 -fstack-protector -D_FORTIFY_SOURCE=2"
+ fi
+ if test "x$GXX" = xyes; then
+ # Remove any -g flags from the command line
+ CXXFLAGS=[`echo $CXXFLAGS | sed 's/-g[^ \t]*[ \t]*//g'`]
+ CXXFLAGS="$CXXFLAGS -ggdb3 -fstack-protector -D_FORTIFY_SOURCE=2"
+ fi
+fi
+
+
+if test "x$GCC" = xyes ; then
+ WARN="-Wall -Wextra -Wunsafe-loop-optimizations -Wstack-protector"
+else
+ WARN=""
+fi
+
+if test "x$GXX" = xyes ; then
+ WARN="-Wall -Wextra -Wunsafe-loop-optimizations -Wstack-protector"
+else
+ WARN=""
+fi
+
+CFLAGS="$CFLAGS $WARN"
+CXXFLAGS="$CXXFLAGS $WARN"
+
+AC_OUTPUT([Makefile
+ src/Makefile])
+
diff --git a/data/simple.frag b/data/simple.frag
new file mode 100644
index 0000000..abdaed5
--- /dev/null
+++ b/data/simple.frag
@@ -0,0 +1,6 @@
+varying vec4 color;
+
+void main(void)
+{
+ gl_FragColor = color;
+}
diff --git a/data/simple.vert b/data/simple.vert
new file mode 100644
index 0000000..5987379
--- /dev/null
+++ b/data/simple.vert
@@ -0,0 +1,11 @@
+uniform mat4 mvp;
+
+attribute vec4 vert_color;
+
+varying vec4 color;
+
+void main(void)
+{
+ gl_Position = mvp * gl_Vertex;
+ color = abs(vert_color);
+}
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..322901c
--- /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 = rotating_cubes
+rotating_cubes_SOURCES = main.cpp shader.cpp
+
+AM_CXXFLAGS= $(GLU3_CFLAGS)
+rotating_cubes_LDADD = $(FRAMEWORK_LIBS) $(GLU3_LIBS)
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..0cc151b
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,529 @@
+/*
+ * 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 <cmath>
+#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 hinge_angle = 0.0f;
+static float orbit_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 get_uniform_locations()
+ {
+ this->mvp_uniform =
+ glGetUniformLocation(this->program, "mvp");
+ }
+
+ GLint mvp_uniform;
+};
+
+static complex_points_program *cube_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->tangent = NULL;
+ this->uv = NULL;
+ this->vertex_count = num_verts;
+
+ this->elt = (GLushort *) &data[2 * 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;
+
+ GLenum mode;
+};
+
+
+static simple_consumer *cube_sink;
+
+/**
+ * Fill a buffer object with data defining a cube.
+ */
+static simple_consumer *
+fill_in_object_data(GLuint buf)
+{
+ GLUcubeProducer cube_source(1.0);
+
+ const unsigned num_verts = cube_source.vertex_count();
+ const unsigned num_elts = cube_source.element_count();
+
+ const unsigned size = (2 * 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);
+ cube_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 15 degrees and 60 degrees per second
+ * respectively. All of the measurements want angles
+ * measured in radians. Do the conversion here.
+ */
+ orbit_angle += degrees_to_radians(15.0 * dt);
+ y_angle += degrees_to_radians(60.0 * dt);
+
+
+ /* The "hinge" rotation angle oscillates between 0
+ * and 45 degrees. Implement this using the sine
+ * function. Bias by 1.0 then scale by 0.5 to get
+ * sine in the range [0, 1], the scale by 45 degrees.
+ * Finally, convert the angle to radians.
+ */
+ const float h = (sin(y_angle) + 1.0) / 2.0;
+ hinge_angle = degrees_to_radians(h * 45.0);
+ }
+ }
+
+ last_t = t;
+
+ /* Storage for all of the transformation matrices.
+ */
+ GLUmat4 transformations[5];
+
+ /* Start by generating the viewing matrix. The camera will orbit
+ * the world-space origin at a distance of 15 units.
+ */
+ const GLUmat4 vp(projection_matrix * gluTranslate(0.0, 0.0, -15.0));
+
+ for (unsigned i = 0; i < ARRAY_SIZE(transformations); i++) {
+ transformations[i] = vp;
+ }
+
+
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+
+ /* Render the five cubes in the scene.
+ */
+ glUseProgram(cube_program->program);
+
+ if (timer_queries[0])
+ glBeginQuery(GL_TIME_ELAPSED, timer_queries[0]);
+
+ for (unsigned i = 0; i < ARRAY_SIZE(transformations); i++) {
+ /* Set the modelview-projection matrix for rendering this
+ * object.
+ */
+ glUniformMatrix4fv(cube_program->mvp_uniform, 1, false,
+ (float *) &transformations[i]);
+
+
+ /* Draw the object.
+ */
+ glDrawElements(cube_sink->mode,
+ cube_sink->elt_count,
+ GL_UNSIGNED_SHORT,
+ BUFFER_OFFSET(cube_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);
+
+ 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 (cube_program)
+ delete cube_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");
+
+ cube_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);
+ cube_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(cube_sink->position_offset));
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0,
+ BUFFER_OFFSET(cube_sink->normal_offset));
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+
+ 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, 25.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) {
+ 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;
+
+ default:
+ break;
+ }
+ } while (SDL_PollEvent(&event));
+
+
+ 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 */