/* * 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 #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; 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++) { transformations[i] = transformations[i - 1] * gluTranslate(0.0f, 2.0f * i, 0.0f) * gluRotate(GLUvec4(1.0f, 0.0f, 0.0f, 0.0f), angle) * gluTranslate(0.0f, -2.0f * i, 0.0f); } 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; }