/* * 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 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; }