// STANDARD class renderer_opengl : public renderer { public: virtual bool uses_opengl() { return true; } protected: SDL_Surface *screen; int dispx, dispy; // Cache of the current font size bool init_video(int w, int h) { // Get ourselves an opengl-enabled SDL window Uint32 flags = SDL_HWSURFACE | SDL_OPENGL; // Set it up for windowed or fullscreen, depending. if (enabler.is_fullscreen()) { flags |= SDL_FULLSCREEN; } else { if (!init.display.flag.has_flag(INIT_DISPLAY_FLAG_NOT_RESIZABLE)) flags |= SDL_RESIZABLE; } // Setup OpenGL attributes SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, init.window.flag.has_flag(INIT_WINDOW_FLAG_VSYNC_ON)); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, init.display.flag.has_flag(INIT_DISPLAY_FLAG_SINGLE_BUFFER) ? 0 : 1); // (Re)create the window screen = SDL_SetVideoMode(w, h, 32, flags); if (!screen) return false; // Test double-buffering status int test; SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &test); if (test != ((init.display.flag.has_flag(INIT_DISPLAY_FLAG_SINGLE_BUFFER)) ? 0 : 1)) { if (enabler.is_fullscreen()); //errorlog << "Requested single-buffering not available\n" << flush; else report_error("OpenGL", "Requested single-buffering not available"); } // (Re)initialize GLEW. Technically only needs to be done once on // linux, but on windows forgetting will cause crashes. glewInit(); // Set the viewport and clear glViewport(0, 0, screen->w, screen->h); glClear(GL_COLOR_BUFFER_BIT); return true; } // Vertexes, foreground color, background color, texture coordinates GLfloat *vertexes, *fg, *bg, *tex; void write_tile_vertexes(GLfloat x, GLfloat y, GLfloat *vertex) { vertex[0] = x; // Upper left vertex[1] = y; vertex[2] = x+1; // Upper right vertex[3] = y; vertex[4] = x; // Lower left vertex[5] = y+1; vertex[6] = x; // Lower left again (triangle 2) vertex[7] = y+1; vertex[8] = x+1; // Upper right vertex[9] = y; vertex[10] = x+1; // Lower right vertex[11] = y+1; } virtual void allocate(int tiles) { vertexes = static_cast(realloc(vertexes, sizeof(GLfloat) * tiles * 2 * 6)); assert(vertexes); fg = static_cast(realloc(fg, sizeof(GLfloat) * tiles * 4 * 6)); assert(fg); bg = static_cast(realloc(bg, sizeof(GLfloat) * tiles * 4 * 6)); assert(bg); tex = static_cast(realloc(tex, sizeof(GLfloat) * tiles * 2 * 6)); assert(tex); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_FLOAT, 0, vertexes); } virtual void init_opengl() { enabler.textures.upload_textures(); } virtual void uninit_opengl() { enabler.textures.remove_uploaded_textures(); } virtual void draw(int vertex_count) { // Render the background colors glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); glColorPointer(4, GL_FLOAT, 0, bg); glDrawArrays(GL_TRIANGLES, 0, vertex_count); // Render the foreground, colors and textures both glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_NOTEQUAL, 0); glEnable(GL_TEXTURE_2D); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexCoordPointer(2, GL_FLOAT, 0, tex); glColorPointer(4, GL_FLOAT, 0, fg); glDrawArrays(GL_TRIANGLES, 0, vertex_count); printGLError(); } void write_tile_arrays(int x, int y, GLfloat *fg, GLfloat *bg, GLfloat *tex) { Either id = screen_to_texid(x, y); if (id.isL) { // An ordinary tile const gl_texpos *txt = enabler.textures.gl_texpos; // TODO: Only bother to set the one that's actually read in flat-shading mode // And set flat-shading mode. for (int i = 0; i < 6; i++) { *(fg++) = id.left.r; *(fg++) = id.left.g; *(fg++) = id.left.b; *(fg++) = 1; *(bg++) = id.left.br; *(bg++) = id.left.bg; *(bg++) = id.left.bb; *(bg++) = 1; } // Set texture coordinates *(tex++) = txt[id.left.texpos].left; // Upper left *(tex++) = txt[id.left.texpos].bottom; *(tex++) = txt[id.left.texpos].right; // Upper right *(tex++) = txt[id.left.texpos].bottom; *(tex++) = txt[id.left.texpos].left; // Lower left *(tex++) = txt[id.left.texpos].top; *(tex++) = txt[id.left.texpos].left; // Lower left *(tex++) = txt[id.left.texpos].top; *(tex++) = txt[id.left.texpos].right; // Upper right *(tex++) = txt[id.left.texpos].bottom; *(tex++) = txt[id.left.texpos].right; // Lower right *(tex++) = txt[id.left.texpos].top; } else { // TODO } } public: void update_tile(int x, int y) { const int tile = x*gps.dimy + y; // Update the arrays GLfloat *fg = this->fg + tile * 4 * 6; GLfloat *bg = this->bg + tile * 4 * 6; GLfloat *tex = this->tex + tile * 2 * 6; write_tile_arrays(x, y, fg, bg, tex); } void update_all() { glClear(GL_COLOR_BUFFER_BIT); for (int x = 0; x < gps.dimx; x++) for (int y = 0; y < gps.dimy; y++) update_tile(x, y); } void render() { draw(gps.dimx*gps.dimy*6); if (init.display.flag.has_flag(INIT_DISPLAY_FLAG_ARB_SYNC) && GL_ARB_sync) { assert(enabler.sync == NULL); enabler.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } SDL_GL_SwapBuffers(); } renderer_opengl() { // Init member variables so realloc'll work screen = NULL; vertexes = NULL; fg = NULL; bg = NULL; tex = NULL; zoom_steps = forced_steps = 0; // Disable key repeat SDL_EnableKeyRepeat(0, 0); // Set window title/icon. SDL_WM_SetCaption(GAME_TITLE_STRING, NULL); SDL_Surface *icon = IMG_Load("data/art/icon.png"); if (icon != NULL) { SDL_WM_SetIcon(icon, NULL); // The icon's surface doesn't get used past this point. SDL_FreeSurface(icon); } // Find the current desktop resolution if fullscreen resolution is auto if (init.display.desired_fullscreen_width == 0 || init.display.desired_fullscreen_height == 0) { const struct SDL_VideoInfo *info = SDL_GetVideoInfo(); init.display.desired_fullscreen_width = info->current_w; init.display.desired_fullscreen_height = info->current_h; } // Initialize our window bool worked = init_video(enabler.is_fullscreen() ? init.display.desired_fullscreen_width : init.display.desired_windowed_width, enabler.is_fullscreen() ? init.display.desired_fullscreen_height : init.display.desired_windowed_height); // Fallback to windowed mode if fullscreen fails if (!worked && enabler.is_fullscreen()) { enabler.fullscreen = false; report_error("SDL initialization failure, trying windowed mode", SDL_GetError()); worked = init_video(init.display.desired_windowed_width, init.display.desired_windowed_height); } // Quit if windowed fails if (!worked) { report_error("SDL initialization failure", SDL_GetError()); exit(EXIT_FAILURE); } // Initialize opengl init_opengl(); } virtual ~renderer_opengl() { free(vertexes); free(fg); free(bg); free(tex); } int zoom_steps, forced_steps; int natural_w, natural_h; // How large our view would be if it wasn't zoomed void zoom(zoom_commands cmd) { pair before = compute_zoom(true); int before_steps = zoom_steps; switch (cmd) { case zoom_in: zoom_steps -= init.input.zoom_speed; break; case zoom_out: zoom_steps += init.input.zoom_speed; break; case zoom_reset: zoom_steps = 0; case zoom_resetgrid: compute_forced_zoom(); break; } pair after = compute_zoom(true); if (after == before && (cmd == zoom_in || cmd == zoom_out)) zoom_steps = before_steps; else reshape(after); } void compute_forced_zoom() { forced_steps = 0; pair zoomed = compute_zoom(); while (zoomed.first < MIN_GRID_X || zoomed.second < MIN_GRID_Y) { forced_steps++; zoomed = compute_zoom(); } while (zoomed.first > MAX_GRID_X || zoomed.second > MAX_GRID_Y) { forced_steps--; zoomed = compute_zoom(); } } pair compute_zoom(bool clamp = false) { const int dispx = enabler.is_fullscreen() ? init.font.large_font_dispx : init.font.small_font_dispx; const int dispy = enabler.is_fullscreen() ? init.font.large_font_dispy : init.font.small_font_dispy; int w, h; if (dispx < dispy) { w = natural_w + zoom_steps + forced_steps; h = double(natural_h) * (double(w) / double(natural_w)); } else { h = natural_h + zoom_steps + forced_steps; w = double(natural_w) * (double(h) / double(natural_h)); } if (clamp) { w = MIN(MAX(w, MIN_GRID_X), MAX_GRID_X); h = MIN(MAX(h, MIN_GRID_Y), MAX_GRID_Y); } return make_pair(w,h); } // Parameters: grid units void reshape(pair size) { int w = MIN(MAX(size.first, MIN_GRID_X), MAX_GRID_X); int h = MIN(MAX(size.second, MIN_GRID_Y), MAX_GRID_Y); #ifdef DEBUG cout << "Resizing grid to " << w << "x" << h << endl; #endif gps_allocate(w, h); reshape_gl(); } int off_x, off_y, size_x, size_y; bool get_mouse_coords(int &x, int &y) { int mouse_x, mouse_y; SDL_GetMouseState(&mouse_x, &mouse_y); mouse_x -= off_x; mouse_y -= off_y; if (mouse_x < 0 || mouse_y < 0 || mouse_x >= size_x || mouse_y >= size_y) return false; // Out of bounds x = double(mouse_x) / double(size_x) * double(gps.dimx); y = double(mouse_y) / double(size_y) * double(gps.dimy); return true; } virtual void reshape_gl() { // Allocate array memory allocate(gps.dimx * gps.dimy); // Initialize the vertex array int tile = 0; for (GLfloat x = 0; x < gps.dimx; x++) for (GLfloat y = 0; y < gps.dimy; y++, tile++) write_tile_vertexes(x, y, vertexes + 6*2*tile); // Setup invariant state glEnableClientState(GL_COLOR_ARRAY); /// Set up our coordinate system if (forced_steps + zoom_steps == 0 && init.display.flag.has_flag(INIT_DISPLAY_FLAG_BLACK_SPACE)) { size_x = gps.dimx * dispx; size_y = gps.dimy * dispy; off_x = (screen->w - size_x) / 2; off_y = (screen->h - size_y) / 2; } else { // If we're zooming (or just not using black space), we use the // entire window. size_x = screen->w; size_y = screen->h; off_x = off_y = 0; } glViewport(off_x, off_y, size_x, size_y); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, gps.dimx, gps.dimy, 0); } // Parameters: window size void resize(int w, int h) { // (Re)calculate grid-size dispx = enabler.is_fullscreen() ? init.font.large_font_dispx : init.font.small_font_dispx; dispy = enabler.is_fullscreen() ? init.font.large_font_dispy : init.font.small_font_dispy; natural_w = MAX(w / dispx,1); natural_h = MAX(h / dispy,1); // Compute forced_steps so we satisfy our grid-size limits compute_forced_zoom(); // Force a full display cycle gps.force_full_display_count = 1; enabler.flag |= ENABLERFLAG_RENDER; // Reinitialize the video uninit_opengl(); init_video(w, h); init_opengl(); // Only reshape if we're free to pick grid size if (enabler.overridden_grid_sizes.size() == 0) reshape(compute_zoom()); } // Parameters: grid size void grid_resize(int w, int h) { reshape(make_pair(w, h)); } public: void set_fullscreen() { if (enabler.is_fullscreen()) { init.display.desired_windowed_width = screen->w; init.display.desired_windowed_height = screen->h; resize(init.display.desired_fullscreen_width, init.display.desired_fullscreen_height); } else { resize(init.display.desired_windowed_width, init.display.desired_windowed_height); } } }; // Specialization for PARTIAL:0 class renderer_once : public renderer_opengl { int tile_count; protected: void update_tile(int x, int y) { write_tile_vertexes(x, y, vertexes + tile_count * 6 * 2); write_tile_arrays(x, y, fg + tile_count * 6 * 4, bg + tile_count * 6 * 4, tex + tile_count * 6 * 2); tile_count++; } void draw(int dummy) { renderer_opengl::draw(tile_count*6); tile_count = 0; } public: renderer_once() { tile_count = 0; } }; // PARTIAL:N class renderer_partial : public renderer_opengl { int buffersz; list erasz; // Previous eras int current_erasz; // And the current one int sum_erasz; int head, tail; // First unused tile, first used tile respectively int redraw_count; // Number of eras to max out at void update_tile(int x, int y) { write_tile_vertexes(x, y, vertexes + head * 6 * 2); write_tile_arrays(x, y, fg + head * 6 * 4, bg + head * 6 * 4, tex + head * 6 * 2); head = (head + 1) % buffersz; current_erasz++; sum_erasz++; if (head == tail) { //gamelog << "Expanding partial-printing buffer" << endl; // Buffer is full, expand it. renderer_opengl::allocate(buffersz * 2); // Move the tail to the end of the newly allocated space tail += buffersz; memmove(vertexes + tail * 6 * 2, vertexes + head * 6 * 2, sizeof(GLfloat) * 6 * 2 * (buffersz - head)); memmove(fg + tail * 6 * 4, fg + head * 6 * 4, sizeof(GLfloat) * 6 * 4 * (buffersz - head)); memmove(bg + tail * 6 * 4, fg + head * 6 * 4, sizeof(GLfloat) * 6 * 4 * (buffersz - head)); memmove(tex + tail * 6 * 2, fg + head * 6 * 2, sizeof(GLfloat) * 6 * 2 * (buffersz - head)); // And finish. buffersz *= 2; } } void allocate(int tile_count) { assert(false); } virtual void reshape_gl() { // TODO: This function is duplicate code w/base class reshape_gl // Setup invariant state glEnableClientState(GL_COLOR_ARRAY); /// Set up our coordinate system if (forced_steps + zoom_steps == 0 && init.display.flag.has_flag(INIT_DISPLAY_FLAG_BLACK_SPACE)) { size_x = gps.dimx * dispx; size_y = gps.dimy * dispy; off_x = (screen->w - size_x) / 2; off_y = (screen->h - size_y) / 2; } else { // If we're zooming (or just not using black space), we use the // entire window. size_x = screen->w; size_y = screen->h; off_x = off_y = 0; } glViewport(off_x, off_y, size_x, size_y); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, gps.dimx, gps.dimy, 0); } void draw_arrays(GLfloat *vertexes, GLfloat *fg, GLfloat *bg, GLfloat *tex, int tile_count) { // Set vertex pointer glVertexPointer(2, GL_FLOAT, 0, vertexes); // Render the background colors glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); glColorPointer(4, GL_FLOAT, 0, bg); glDrawArrays(GL_TRIANGLES, 0, tile_count * 6); // Render the foreground, colors and textures both glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_NOTEQUAL, 0); glEnable(GL_TEXTURE_2D); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColorPointer(4, GL_FLOAT, 0, fg); glTexCoordPointer(2, GL_FLOAT, 0, tex); glDrawArrays(GL_TRIANGLES, 0, tile_count * 6); } void draw(int dummy) { if (tail > head) { // We're straddling the end of the array, so have to do this in two steps draw_arrays(vertexes + tail * 6 * 2, fg + tail * 6 * 4, bg + tail * 6 * 4, tex + tail * 6 * 2, buffersz - tail); draw_arrays(vertexes, fg, bg, tex, head-1); } else { draw_arrays(vertexes + tail * 6 * 2, fg + tail * 6 * 4, bg + tail * 6 * 4, tex + tail * 6 * 2, sum_erasz); } printGLError(); erasz.push_back(current_erasz); current_erasz = 0; if (erasz.size() == redraw_count) { // Right, time to retire the oldest era. tail = (tail + erasz.front()) % buffersz; sum_erasz -= erasz.front(); erasz.pop_front(); } } public: renderer_partial() { redraw_count = init.display.partial_print_count; buffersz = 2048; renderer_opengl::allocate(buffersz); current_erasz = head = tail = sum_erasz = 0; } }; class renderer_accum_buffer : public renderer_once { void draw(int vertex_count) { // Copy the previous frame's buffer back in glAccum(GL_RETURN, 1); renderer_once::draw(vertex_count); // Store the screen contents back to the buffer glAccum(GL_LOAD, 1); } }; class renderer_framebuffer : public renderer_once { GLuint framebuffer, fb_texture; void init_opengl() { glGenFramebuffersEXT(1, &framebuffer); // Allocate FBO texture memory glGenTextures(1, &fb_texture); glBindTexture(GL_TEXTURE_2D, fb_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screen->w, screen->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // Bind texture to FBO glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, framebuffer); glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, fb_texture, 0); renderer_once::init_opengl(); } void uninit_opengl() { renderer_once::uninit_opengl(); glDeleteTextures(1, &fb_texture); glDeleteFramebuffersEXT(1, &framebuffer); } void draw(int vertex_count) { // Bind the framebuffer glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, framebuffer); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); // Draw renderer_once::draw(vertex_count); // Draw the framebuffer to screen glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, framebuffer); glBlitFramebufferEXT(0,0, screen->w, screen->h, 0,0, screen->w, screen->h, GL_COLOR_BUFFER_BIT, GL_NEAREST); printGLError(); } }; class renderer_vbo : public renderer_opengl { GLuint vbo; // Vertexes only void init_opengl() { renderer_opengl::init_opengl(); glGenBuffersARB(1, &vbo); glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo); glBufferDataARB(GL_ARRAY_BUFFER_ARB, gps.dimx*gps.dimy*6*2*sizeof(GLfloat), vertexes, GL_STATIC_DRAW_ARB); glVertexPointer(2, GL_FLOAT, 0, 0); glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); } void uninit_opengl() { glDeleteBuffersARB(1, &vbo); renderer_opengl::uninit_opengl(); } };