/* * Copyright © 2009 Eric Anholt * 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. * * Authors: * Eric Anholt * */ #include #include #include #include #include #include #include #include #include #include #include "glass.h" #include #define DEFAULT_WIDTH 600 #define DEFAULT_HEIGHT 700 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) static int win_width, win_height; enum shadowmap_display_uniform_list { SHADOWMAP_DISPLAY_UNIFORM_SHADOW_SAMPLER, SHADOWMAP_DISPLAY_UNIFORM_MAX }; struct uniform_desc uniforms[UNIFORM_MAX] = { [UNIFORM_MV] = { "mv" }, [UNIFORM_MVP] = { "mvp" }, [UNIFORM_LIGHT_MVP] = { "light_mvp" }, [UNIFORM_LIGHT_EYE] = { "light_eye" }, [UNIFORM_NORMAL_SAMPLER] = { "normal_sampler" }, [UNIFORM_HEIGHTMAP_SAMPLER] = { "heightmap_sampler" }, [UNIFORM_SHADOW_SAMPLER] = { "shadow_sampler" }, [UNIFORM_NI] = { "ni" }, [UNIFORM_F0] = { "F0" }, [UNIFORM_WARD_MM_INV] = { "ward_mm_inv" }, [UNIFORM_WARD_MN_INV] = { "ward_mn_inv" }, [UNIFORM_WARD_NN_INV] = { "ward_nn_inv" }, }; struct uniform_desc ground_uniforms[GROUND_UNIFORM_MAX] = { [GROUND_UNIFORM_MV] = { "mv" }, [GROUND_UNIFORM_MVP] = { "mvp" }, [GROUND_UNIFORM_LIGHT_EYE] = { "light_eye" }, [GROUND_UNIFORM_LIGHT_MVP] = { "light_mvp" }, [GROUND_UNIFORM_SHADOW_SAMPLER] = { "shadow_sampler" }, }; struct uniform_desc shadow_uniforms[SHADOW_UNIFORM_MAX] = { [SHADOW_UNIFORM_MVP] = { "mvp" }, }; struct uniform_desc shadowmap_display_uniforms[SHADOWMAP_DISPLAY_UNIFORM_MAX] = { [SHADOWMAP_DISPLAY_UNIFORM_SHADOW_SAMPLER] = { "shadow_sampler" }, }; static GLuint shadowmap_display_prog = 0; GLuint glass_prog = 0, ground_prog = 0, shadow_prog = 0; static GLboolean display_shadow_map = GL_FALSE; static const GLUvec4 light_start_world = {{0.0, 10.0, 25.0, 1.0}}; static const GLUvec4 x_axis = {{1.0, 0.0, 0.0, 1.0}}; static const GLUvec4 y_axis = {{0.0, 1.0, 0.0, 1.0}}; static const GLUvec4 z_axis = {{0.0, 0.0, 1.0, 1.0}}; GLUvec4 eye_world = {{0.0, 0.0, 2.0, 1.0}}; static GLUvec4 eye_center_world = {{0.0, 1.0, 2.0, 1.0}}; GLUvec4 light_eye; GLUvec4 light_world; GLUmat4 projection; GLUmat4 world_to_eye; GLUmat4 ring_obj_to_world[NUM_RINGS]; static time_t start_tv_sec = 0; static float start_time, cur_time, last_fps_time = 0; static int frames = 0, last_fps_frames = 0; /* Timer queries */ static GLboolean timers = GL_FALSE; static float clear_time = 0, shadow_generate_time = 0; static float rings_draw_time = 0, ground_draw_time = 0; static GLuint timer_query; int no_multi_draw_arrays = 0, benchmark = 0; static SDL_Surface *sdl_surf = NULL; static GLboolean done = GL_FALSE; static double get_time_in_secs(void) { struct timeval tv; gettimeofday(&tv, NULL); /* Return get_time_in_secs() based off of the seconds when it's first * called. Otherwise, when we return a float we lose too much * precision. */ if (start_tv_sec == 0) start_tv_sec = tv.tv_sec; return (double)(tv.tv_sec - start_tv_sec) + tv.tv_usec / 1000000.0; } static void update_light_position(void) { GLUmat4 light_eye_matrix, light_world_matrix, temp; float light_rotation_rads; /* Set the light position: Rotate around the Y axis from its original * position every 4 seconds. */ light_rotation_rads = cur_time * 2 * M_PI / 4; gluRotate4v(&light_world_matrix, &z_axis, light_rotation_rads); gluMult4m_4v(&light_world, &light_world_matrix, &light_start_world); /* print_GLUvec4("light_start_world", &light_start_world); print_GLUvec4("z_axis", &z_axis); print_GLUvec4("light_world", &light_world); print_GLUmat4("light_world_matrix", &light_world_matrix); */ gluTranslate3f(&temp, -eye_world.values[0], -eye_world.values[1], -eye_world.values[2]); gluMult4m_4m(&light_eye_matrix, &temp, &light_world_matrix); gluMult4m_4v(&light_eye, &light_eye_matrix, &light_start_world); light_eye.values[0] /= light_eye.values[3]; light_eye.values[1] /= light_eye.values[3]; light_eye.values[2] /= light_eye.values[3]; light_eye.values[3] = 1.0; } static void calc_new_ring_transforms(int instance) { int x_index = (instance / 3) / 3 - 1; int y_index = (instance / 3) % 3 - 1; int z_index = instance % 3; GLUmat4 obj_to_world, temp; /* Have the ring spinning on its axis slowly. */ gluRotate4v(&obj_to_world, &z_axis, cur_time * 2 * M_PI / 20); /* Make them wobble a little over time. */ gluRotate4v(&temp, &x_axis, M_PI / 8 * sin(cur_time * 2 * M_PI / 3)); gluMult4m_4m(&obj_to_world, &temp, &obj_to_world); gluRotate4v(&temp, &y_axis, M_PI / 16 * sin(cur_time * 2 * M_PI / 5)); gluMult4m_4m(&obj_to_world, &temp, &obj_to_world); /* Have the vertical rows start with different alignments. */ switch (z_index) { case 0: break; case 1: gluRotate4v(&temp, &x_axis, M_PI / 4); gluMult4m_4m(&obj_to_world, &temp, &obj_to_world); break; case 2: gluRotate4v(&temp, &x_axis, -M_PI / 4); gluMult4m_4m(&obj_to_world, &temp, &obj_to_world); break; } /* Move them out into their position in the cube. */ gluTranslate3f(&temp, x_index * 5.0, 10 + y_index * 5.0, 4.0 + z_index * 5.0); gluMult4m_4m(&ring_obj_to_world[instance], &temp, &obj_to_world); /* Calculate the bounding sphere of the set of rings, for use in * shadow mapping. */ ring_bounding_sphere_center_world.values[0] = 0; ring_bounding_sphere_center_world.values[1] = 10; ring_bounding_sphere_center_world.values[2] = 4 + 5; ring_bounding_sphere_center_world.values[3] = 1.0; /* The rings are roughly 2.5 wide from their center, and they can * be oriented in pretty much any direction, and the farthest out * would be the corners of the cubes (x/y_index +1 or -1, z_index * 0 or 2). * * So we're just making a bounding box and doing a bounding sphere * of it, really. */ ring_bounding_sphere_radius = sqrt((5 + ring.radius) * (5 + ring.radius) * 3); } static void draw_shadow_map(void) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, shadow_tex); glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); if (shadowmap_display_prog != 0) { GLfloat vertices[] = { -1, -1, 1, -1, 1, 1, -1, 1, }; glUseProgram(shadowmap_display_prog); glBindBuffer(GL_ARRAY_BUFFER_ARB, 0); glBindVertexArray(0); glVertexPointer(2, GL_FLOAT, 0, vertices); glEnable(GL_VERTEX_ARRAY); glDrawArrays(GL_QUADS, 0, 4); glDisable(GL_VERTEX_ARRAY); } glEnable(GL_DEPTH_TEST); glActiveTexture(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); } static void start_timer_query(void) { if (!timers) return; if (!timer_query) glGenQueries(1, &timer_query); glBeginQuery(GL_TIME_ELAPSED_EXT, timer_query); } static void end_timer_query(float *time) { GLint passed; if (!timers) return; glEndQuery(GL_TIME_ELAPSED_EXT); glGetQueryObjectiv(timer_query, GL_QUERY_RESULT, &passed); *time += passed / 1000000000.0; } static void report_timers(void) { if (!timers) return; printf(" clear time: %.2f secs\n" " shadow time: %.2f secs\n" " rings time: %.2f secs\n" " ground time: %.2f secs\n", clear_time, shadow_generate_time, rings_draw_time, ground_draw_time); clear_time = 0; shadow_generate_time = 0; rings_draw_time = 0; ground_draw_time = 0; } static void draw(void) { int instance; start_timer_query(); glClearColor(0.0, 0.0, 0.8, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); end_timer_query(&clear_time); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); update_light_position(); for (instance = 0; instance < NUM_RINGS; instance++) calc_new_ring_transforms(instance); start_timer_query(); generate_rings_shadowmap(); end_timer_query(&shadow_generate_time); glViewport(0, 0, win_width, win_height); if (display_shadow_map) { draw_shadow_map(); } else { start_timer_query(); draw_rings(); end_timer_query(&rings_draw_time); start_timer_query(); draw_ground(); end_timer_query(&ground_draw_time); } SDL_GL_SwapBuffers(); frames++; if (benchmark && frames >= 5 * 60) { float total_time; glFinish(); total_time = get_time_in_secs() - start_time; printf("%d frames in %.2f secs: %.1f fps\n", frames, total_time, frames / total_time) ; report_timers(); done = GL_TRUE; } } static void reshape(int width, int height) { win_width = width; win_height = height; sdl_surf = SDL_SetVideoMode(width, height, 0, SDL_OPENGL | SDL_RESIZABLE); if (sdl_surf == NULL) errx(1, "video mode set fail\n"); gluPerspective4f(&projection, 80, (float)win_width / (float)win_height, 0.2, 40); } static GLint compile_program(GLenum type, const char *filename) { GLint prog; GLint ok; struct stat st; int err; GLchar *prog_string; FILE *f; err = stat(filename, &st); if (err == -1) { fprintf(stderr, "Couldn't stat program: %s\n", strerror(errno)); exit(1); } prog_string = malloc(st.st_size + 1); if (prog_string == NULL) { fprintf(stderr, "malloc\n"); exit(1); } f = fopen(filename, "ro"); if (f == NULL) { fprintf(stderr, "Couldn't open program: %s\n", strerror(errno)); exit(1); } fread(prog_string, 1, st.st_size, f); prog_string[st.st_size] = '\0'; fclose(f); prog = glCreateShader(type); glShaderSource(prog, 1, (const GLchar **)&prog_string, NULL); glCompileShader(prog); glGetShaderiv(prog, GL_COMPILE_STATUS, &ok); if (!ok) { GLchar *info; GLint size; glGetShaderiv(prog, GL_INFO_LOG_LENGTH, &size); info = malloc(size); glGetShaderInfoLog(prog, size, NULL, info); fprintf(stderr, "Failed to compile %s: %s\n", type == GL_FRAGMENT_SHADER ? "FS" : "VS", info); } free(prog_string); return prog; } static GLuint load_program(char *base_filename, struct uniform_desc *uniforms, int num_uniforms) { GLint prog, fs, vs, ok; char vert_filename[100]; char frag_filename[100]; int i; sprintf(vert_filename, "%s.vert", base_filename); sprintf(frag_filename, "%s.frag", base_filename); vs = compile_program(GL_VERTEX_SHADER, vert_filename); fs = compile_program(GL_FRAGMENT_SHADER, frag_filename); prog = glCreateProgram(); glAttachShader(prog, fs); glAttachShader(prog, vs); glLinkProgram(prog); glGetProgramiv(prog, GL_LINK_STATUS, &ok); if (!ok) { GLchar *info; GLint size; glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &size); info = malloc(size); glGetProgramInfoLog(prog, size, NULL, info); fprintf(stderr, "Failed to link: %s\n", info); glDeleteProgram(prog); return 0; } glUseProgram(prog); for (i = 0; i < num_uniforms; i++) { uniforms[i].location = glGetUniformLocation(prog, uniforms[i].name); } return prog; } static void load_glass_program(void) { if (glass_prog != 0) glDeleteProgram(glass_prog); glass_prog = load_program("glass", uniforms, ARRAY_SIZE(uniforms)); glUniform1i(uniforms[UNIFORM_NORMAL_SAMPLER].location, 0); glUniform1i(uniforms[UNIFORM_HEIGHTMAP_SAMPLER].location, 1); glUniform1i(uniforms[UNIFORM_SHADOW_SAMPLER].location, 2); update_brdf_constants(); } static void load_ground_program(void) { if (ground_prog != 0) glDeleteProgram(ground_prog); ground_prog = load_program("ground", ground_uniforms, ARRAY_SIZE(ground_uniforms)); glUniform1i(ground_uniforms[GROUND_UNIFORM_SHADOW_SAMPLER].location, 0); } static void load_shadow_program(void) { if (shadow_prog != 0) glDeleteProgram(shadow_prog); shadow_prog = load_program("shadow", shadow_uniforms, ARRAY_SIZE(shadow_uniforms)); } static void load_shadowmap_display_program(void) { if (shadowmap_display_prog != 0) glDeleteProgram(shadowmap_display_prog); shadowmap_display_prog = load_program("shadowmap_display", shadowmap_display_uniforms, ARRAY_SIZE(shadowmap_display_uniforms)); glUniform1i(shadowmap_display_uniforms[SHADOWMAP_DISPLAY_UNIFORM_SHADOW_SAMPLER].location, 0); } static void load_programs(void) { load_glass_program(); load_ground_program(); load_shadow_program(); load_shadowmap_display_program(); } static void init(void) { reshape(DEFAULT_WIDTH, DEFAULT_HEIGHT); glewInit(); if (!GLEW_VERSION_2_0) errx(1, "Requires GL 2.0\n"); if (!GLEW_ARB_vertex_array_object) errx(1, "Requires ARB_vertex_array_object\n"); /* Have the eye out in the world, sitting above the ground on the * Z axis, looking down y. */ gluLookAt4v(&world_to_eye, &eye_world, &eye_center_world, &z_axis); setup_ground(); setup_rings(); load_programs(); } /** * Push an event each time the timer callback fires. */ static uint32_t timer_callback(uint32_t 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; } static void idle(void) { static float bench_time = 0; if (benchmark) { cur_time = bench_time; bench_time += 1.0 / 60.0; } else { cur_time = get_time_in_secs() - start_time; if (cur_time > last_fps_time + 5) { printf("%d frames in %.2f secs: %.1f fps\n", frames - last_fps_frames, cur_time - last_fps_time, (frames - last_fps_frames) / (cur_time - last_fps_time)); last_fps_time = cur_time; last_fps_frames = frames; report_timers(); } } } static void key(SDLKey sym) { switch (sym) { case 'c': load_programs(); break; case 'm': display_shadow_map = !display_shadow_map; break; case 'f': SDL_WM_ToggleFullScreen(sdl_surf); break; case SDLK_ESCAPE: done = GL_TRUE; break; default: break; } } static void usage(char *program) { fprintf(stderr, "usage: %s [-nomultidraw] [-benchmark] [-timers]\n", program); exit(1); } static void sdl_event(SDL_Event *event) { static int shift_status = 0; switch (event->type) { case SDL_VIDEORESIZE: reshape(event->resize.w, event->resize.h); break; case SDL_QUIT: done = GL_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; case SDL_MOUSEBUTTONDOWN: break; case SDL_MOUSEBUTTONUP: break; case SDL_MOUSEMOTION: break; case SDL_VIDEOEXPOSE: break; default: if (event->type != SDL_USEREVENT) { printf("event = 0x%04x\n", event->type); } break; } } int main(int argc, char **argv) { int i; start_time = get_time_in_secs(); last_fps_time = start_time; if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) errx(1, "sdl init fail"); atexit(SDL_Quit); (void)SDL_AddTimer(10, timer_callback, NULL); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-nomultidraw") == 0) no_multi_draw_arrays = 1; else if (strcmp(argv[i], "-timers") == 0) timers = 1; else if (strcmp(argv[i], "-benchmark") == 0) benchmark = 1; else usage(argv[0]); } init(); if (timers && !GLEW_EXT_timer_query) { errx(1, "Requires EXT_timer_query\n"); } while (!done) { SDL_Event event; SDL_WaitEvent(&event); do { sdl_event(&event); } while (SDL_PollEvent(&event)); idle(); draw(); } return 0; }