/* * 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 #include #include #include #include #include "shader.h" #define NUM_POINTS 57 static SDL_Surface *my_surf = NULL; static bool anim = true; static bool done = false; static int shift_status = 0; static float angle_offset = 0.0f; static GLuint buf = 0; 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->angle_uniform = glGetUniformLocation(this->program, "rotation_angle"); this->matrix_uniform = glGetUniformLocation(this->program, "m"); } GLint angle_uniform; GLint matrix_uniform; }; static complex_points_program *explicit_rotation = 0; static complex_points_program *angle_addition = 0; static complex_points_program *matrix_rotation = 0; static GLuint timer_queries[3] = { 0, 0, 0 }; #define BUFFER_OFFSET(i) ((char *)0 + (i)) #define CHECK_GL_ERROR(e) \ do { \ e = glGetError(); \ if (e != 0) { \ printf("%s:%u: GL error = 0x%04x\n", __func__, \ __LINE__, e); \ } \ } while (0) static void Redisplay(void) { const float m[4] = { 1.0, 0.0, 0.0, 1.0 }; /* Set the viewport to the whole window and clear the screen. */ glViewport(0, 0, my_surf->w, my_surf->h); glClear(GL_COLOR_BUFFER_BIT); /* Reset the viewport ot the lower left quadrant and draw using the * "explicit rotation" shader. This shader uses the vertex data * stored as (real, imaginary) pairs. */ glViewport(0, 0, my_surf->w / 2, my_surf->h / 2); glUseProgram(explicit_rotation->program); glUniform1f(explicit_rotation->angle_uniform, angle_offset); glUniformMatrix2fv(explicit_rotation->matrix_uniform, 1, false, m); if (timer_queries[0]) glBeginQuery(GL_TIME_ELAPSED, timer_queries[0]); glDrawArrays(GL_POINTS, 0, NUM_POINTS); if (timer_queries[0]) glEndQuery(GL_TIME_ELAPSED); /* Reset the viewport to the upper left quadrant and draw using the * "angle addition" shader. This shader uses the vertex data stored * as (modulus, angle) pairs. */ glViewport(0, my_surf->h / 2, my_surf->w / 2, my_surf->h / 2); glUseProgram(angle_addition->program); glUniform1f(angle_addition->angle_uniform, angle_offset); glUniformMatrix2fv(angle_addition->matrix_uniform, 1, false, m); if (timer_queries[1]) glBeginQuery(GL_TIME_ELAPSED, timer_queries[1]); glDrawArrays(GL_POINTS, NUM_POINTS, NUM_POINTS); if (timer_queries[1]) glEndQuery(GL_TIME_ELAPSED); /* Reset the viewport to the upper right quadrant and draw using the * "matrix rotation" shader. This shader uses the vertex data stored * as (real, imaginary) pairs. */ glViewport(my_surf->w / 2, my_surf->h / 2, my_surf->w / 2, my_surf->h / 2); glUseProgram(matrix_rotation->program); glUniform1f(matrix_rotation->angle_uniform, angle_offset); glUniformMatrix2fv(matrix_rotation->matrix_uniform, 1, false, m); if (timer_queries[2]) glBeginQuery(GL_TIME_ELAPSED, timer_queries[2]); glDrawArrays(GL_POINTS, 0, NUM_POINTS); if (timer_queries[2]) glEndQuery(GL_TIME_ELAPSED); SDL_GL_SwapBuffers(); assert((timer_queries[0] == 0) == (timer_queries[1] == 0)); if (timer_queries[0]) { static GLuint64 totals[3] = { 0, 0, 0 }; static unsigned count = 0; GLuint64 result[3] = { 0, 0, 0 }; for (unsigned i = 0; i < 3; i++) { glGetQueryObjectui64vEXT(timer_queries[i], GL_QUERY_RESULT, &result[i]); totals[i] += result[i]; } count++; if (count > 100) { printf("Timings: %lu, %lu, %lu\n", totals[0] / 100, totals[1] / 100, totals[2] / 100); memset(totals, 0, sizeof(totals)); count = 0; } } } static void Idle(void) { static Uint32 last_t = ~0; Uint32 t = SDL_GetTicks(); if (last_t != (Uint32)~0) { float dt = (float) (t - last_t) / 1000.0f; if (anim) { angle_offset += M_PI * dt; } } last_t = t; } 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) { GLuint vs[3]; GLuint fs; /* If there are dangling instances of these program hanging around, * clean them up. */ if (explicit_rotation) delete explicit_rotation; if (angle_addition) delete angle_addition; if (matrix_rotation) delete matrix_rotation; /* Compile all of the shaders */ vs[0] = compile_shader_from_file(GL_VERTEX_SHADER, "explicit_rotation.vert"); vs[1] = compile_shader_from_file(GL_VERTEX_SHADER, "angle_addition.vert"); vs[2] = compile_shader_from_file(GL_VERTEX_SHADER, "matrix_rotation.vert"); fs = compile_shader_from_file(GL_FRAGMENT_SHADER, "ellipse.frag"); /* Create all of the programs that use the previously created shaders. */ explicit_rotation = create_program(vs[0], fs, "explicit rotation"); angle_addition = create_program(vs[1], fs, "angle addition"); matrix_rotation = create_program(vs[2], fs, "matrix rotation"); /* The individual shaders have been attached to the programs, so they * can safely be deleted. */ glDeleteShader(vs[0]); glDeleteShader(vs[1]); glDeleteShader(vs[2]); 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(3, timer_queries); gluInitializeCompiler(); explicit_rotation = 0; angle_addition = 0; matrix_rotation = 0; build_all_shaders(); float data[2 * (2 * NUM_POINTS)]; for (unsigned i = 0; i < NUM_POINTS; i++) { const float angle = float(i) * M_PI * 2.0f / float(NUM_POINTS); const float r = cos(5.0f * angle) * 0.4f + 0.5f; data[(i + 0) * 2 + 0] = r * cos(angle); data[(i + 0) * 2 + 1] = r * sin(angle); data[(i + NUM_POINTS) * 2 + 0] = r; data[(i + NUM_POINTS) * 2 + 1] = angle; } glGenBuffers(1, &buf); glBindBuffer(GL_ARRAY_BUFFER, buf); glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), BUFFER_OFFSET(0)); glEnableVertexAttribArray(0); glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); printf("GL_RENDERER = %s\n", (const char *) glGetString(GL_RENDERER)); printf("Keyboard input:\n" " a: Toggle animation of object.\n" " c: Re-load and compile shader program code.\n" " ESC: Exit program.\n"); } static void Reshape(int w, int h) { my_surf = SDL_SetVideoMode(w, h, 0, SDL_OPENGL | SDL_RESIZABLE); if (my_surf == NULL) { exit(1); } glViewport(0, 0, my_surf->w, my_surf->h); Init(); } static void Key(SDLKey sym) { switch (sym) { case SDLK_ESCAPE: done = true; 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); 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: if ((event.key.keysym.sym == SDLK_RSHIFT) || (event.key.keysym.sym == SDLK_LSHIFT)) { shift_status++; } else { Key(event.key.keysym.sym); } break; case SDL_KEYUP: if ((event.key.keysym.sym == SDLK_RSHIFT) || (event.key.keysym.sym == SDLK_LSHIFT)) { shift_status--; } /* By clamping the shift status value to 0 we * prevent some bugs when the program is * started with one or both of the shift keys * held down. */ if (shift_status < 0) { shift_status = 0; } break; default: break; } } while (SDL_PollEvent(&event)); Idle(); Redisplay(); } SDL_RemoveTimer(timer_id); SDL_Quit(); return 0; }