/* * 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 #include #include #include #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->k_uniform = glGetUniformLocation(this->program, "k_uniform"); this->light_dir_cs_uniform = glGetUniformLocation(this->program, "light_dir_cs"); } GLint mvp_uniform; GLint mv_normal_uniform; GLint k_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); float k_uniform[4][4][3]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { for (int k = 0; k < 3; k++) { k_uniform[i][j][k] = control_points[4 * i + j].values[k]; } } } glUniformMatrix4x3fv(patch_program->k_uniform, 4, false, (const float *)k_uniform); 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; }