README: explain further musl-specific tweaks
[rofl0r-df-libgraphics.git] / g_src / renderer_opengl.hpp
blob4a616ca9bbf08797efabb8b8261d858b433264fd
1 #ifdef WANT_GL
2 // STANDARD
3 class renderer_opengl : public renderer {
4 public:
5 virtual bool uses_opengl() { return true; }
7 protected:
8 SDL_Surface *screen;
10 int dispx, dispy; // Cache of the current font size
12 bool init_video(int w, int h) {
13 // Get ourselves an opengl-enabled SDL window
14 Uint32 flags = SDL_HWSURFACE | SDL_OPENGL;
16 // Set it up for windowed or fullscreen, depending.
17 if (enabler.is_fullscreen()) {
18 flags |= SDL_FULLSCREEN;
19 } else {
20 if (!init.display.flag.has_flag(INIT_DISPLAY_FLAG_NOT_RESIZABLE))
21 flags |= SDL_RESIZABLE;
24 // Setup OpenGL attributes
25 SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, init.window.flag.has_flag(INIT_WINDOW_FLAG_VSYNC_ON));
26 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,
27 init.display.flag.has_flag(INIT_DISPLAY_FLAG_SINGLE_BUFFER) ? 0 : 1);
29 // (Re)create the window
30 screen = SDL_SetVideoMode(w, h, 32, flags);
32 if (!screen) return false;
34 // Test double-buffering status
35 int test;
36 SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &test);
37 if (test != ((init.display.flag.has_flag(INIT_DISPLAY_FLAG_SINGLE_BUFFER)) ? 0 : 1)) {
38 if (enabler.is_fullscreen());
39 //errorlog << "Requested single-buffering not available\n" << flush;
40 else
41 report_error("OpenGL", "Requested single-buffering not available");
44 // (Re)initialize GLEW. Technically only needs to be done once on
45 // linux, but on windows forgetting will cause crashes.
46 glewInit();
48 // Set the viewport and clear
49 glViewport(0, 0, screen->w, screen->h);
50 glClear(GL_COLOR_BUFFER_BIT);
52 return true;
55 // Vertexes, foreground color, background color, texture coordinates
56 GLfloat *vertexes, *fg, *bg, *tex;
58 void write_tile_vertexes(GLfloat x, GLfloat y, GLfloat *vertex) {
59 vertex[0] = x; // Upper left
60 vertex[1] = y;
61 vertex[2] = x+1; // Upper right
62 vertex[3] = y;
63 vertex[4] = x; // Lower left
64 vertex[5] = y+1;
65 vertex[6] = x; // Lower left again (triangle 2)
66 vertex[7] = y+1;
67 vertex[8] = x+1; // Upper right
68 vertex[9] = y;
69 vertex[10] = x+1; // Lower right
70 vertex[11] = y+1;
73 virtual void allocate(int tiles) {
74 vertexes = static_cast<GLfloat*>(realloc(vertexes, sizeof(GLfloat) * tiles * 2 * 6));
75 assert(vertexes);
76 fg = static_cast<GLfloat*>(realloc(fg, sizeof(GLfloat) * tiles * 4 * 6));
77 assert(fg);
78 bg = static_cast<GLfloat*>(realloc(bg, sizeof(GLfloat) * tiles * 4 * 6));
79 assert(bg);
80 tex = static_cast<GLfloat*>(realloc(tex, sizeof(GLfloat) * tiles * 2 * 6));
81 assert(tex);
83 glEnableClientState(GL_VERTEX_ARRAY);
84 glVertexPointer(2, GL_FLOAT, 0, vertexes);
87 virtual void init_opengl() {
88 enabler.textures.upload_textures();
91 virtual void uninit_opengl() {
92 enabler.textures.remove_uploaded_textures();
95 virtual void draw(int vertex_count) {
96 // Render the background colors
97 glDisable(GL_TEXTURE_2D);
98 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
99 glDisable(GL_BLEND);
100 glDisable(GL_ALPHA_TEST);
101 glColorPointer(4, GL_FLOAT, 0, bg);
102 glDrawArrays(GL_TRIANGLES, 0, vertex_count);
103 // Render the foreground, colors and textures both
104 glEnable(GL_ALPHA_TEST);
105 glAlphaFunc(GL_NOTEQUAL, 0);
106 glEnable(GL_TEXTURE_2D);
107 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
108 glEnable(GL_BLEND);
109 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
110 glTexCoordPointer(2, GL_FLOAT, 0, tex);
111 glColorPointer(4, GL_FLOAT, 0, fg);
112 glDrawArrays(GL_TRIANGLES, 0, vertex_count);
114 printGLError();
117 void write_tile_arrays(int x, int y, GLfloat *fg, GLfloat *bg, GLfloat *tex) {
118 Either<texture_fullid,texture_ttfid> id = screen_to_texid(x, y);
119 if (id.isL) { // An ordinary tile
120 const gl_texpos *txt = enabler.textures.gl_texpos;
121 // TODO: Only bother to set the one that's actually read in flat-shading mode
122 // And set flat-shading mode.
123 for (int i = 0; i < 6; i++) {
124 *(fg++) = id.left.r;
125 *(fg++) = id.left.g;
126 *(fg++) = id.left.b;
127 *(fg++) = 1;
129 *(bg++) = id.left.br;
130 *(bg++) = id.left.bg;
131 *(bg++) = id.left.bb;
132 *(bg++) = 1;
134 // Set texture coordinates
135 *(tex++) = txt[id.left.texpos].left; // Upper left
136 *(tex++) = txt[id.left.texpos].bottom;
137 *(tex++) = txt[id.left.texpos].right; // Upper right
138 *(tex++) = txt[id.left.texpos].bottom;
139 *(tex++) = txt[id.left.texpos].left; // Lower left
140 *(tex++) = txt[id.left.texpos].top;
142 *(tex++) = txt[id.left.texpos].left; // Lower left
143 *(tex++) = txt[id.left.texpos].top;
144 *(tex++) = txt[id.left.texpos].right; // Upper right
145 *(tex++) = txt[id.left.texpos].bottom;
146 *(tex++) = txt[id.left.texpos].right; // Lower right
147 *(tex++) = txt[id.left.texpos].top;
148 } else {
149 // TODO
153 public:
154 void update_tile(int x, int y) {
155 const int tile = x*gps.dimy + y;
156 // Update the arrays
157 GLfloat *fg = this->fg + tile * 4 * 6;
158 GLfloat *bg = this->bg + tile * 4 * 6;
159 GLfloat *tex = this->tex + tile * 2 * 6;
160 write_tile_arrays(x, y, fg, bg, tex);
163 void update_all() {
164 glClear(GL_COLOR_BUFFER_BIT);
165 for (int x = 0; x < gps.dimx; x++)
166 for (int y = 0; y < gps.dimy; y++)
167 update_tile(x, y);
170 void render() {
171 draw(gps.dimx*gps.dimy*6);
172 if (init.display.flag.has_flag(INIT_DISPLAY_FLAG_ARB_SYNC) && GL_ARB_sync) {
173 assert(enabler.sync == NULL);
174 enabler.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
176 SDL_GL_SwapBuffers();
179 renderer_opengl() {
180 // Init member variables so realloc'll work
181 screen = NULL;
182 vertexes = NULL;
183 fg = NULL;
184 bg = NULL;
185 tex = NULL;
186 zoom_steps = forced_steps = 0;
188 // Disable key repeat
189 SDL_EnableKeyRepeat(0, 0);
190 // Set window title/icon.
191 SDL_WM_SetCaption(GAME_TITLE_STRING, NULL);
192 SDL_Surface *icon = IMG_Load("data/art/icon.png");
193 if (icon != NULL) {
194 SDL_WM_SetIcon(icon, NULL);
195 // The icon's surface doesn't get used past this point.
196 SDL_FreeSurface(icon);
199 // Find the current desktop resolution if fullscreen resolution is auto
200 if (init.display.desired_fullscreen_width == 0 ||
201 init.display.desired_fullscreen_height == 0) {
202 const struct SDL_VideoInfo *info = SDL_GetVideoInfo();
203 init.display.desired_fullscreen_width = info->current_w;
204 init.display.desired_fullscreen_height = info->current_h;
207 // Initialize our window
208 bool worked = init_video(enabler.is_fullscreen() ?
209 init.display.desired_fullscreen_width :
210 init.display.desired_windowed_width,
211 enabler.is_fullscreen() ?
212 init.display.desired_fullscreen_height :
213 init.display.desired_windowed_height);
215 // Fallback to windowed mode if fullscreen fails
216 if (!worked && enabler.is_fullscreen()) {
217 enabler.fullscreen = false;
218 report_error("SDL initialization failure, trying windowed mode", SDL_GetError());
219 worked = init_video(init.display.desired_windowed_width,
220 init.display.desired_windowed_height);
222 // Quit if windowed fails
223 if (!worked) {
224 report_error("SDL initialization failure", SDL_GetError());
225 exit(EXIT_FAILURE);
228 // Initialize opengl
229 init_opengl();
232 virtual ~renderer_opengl() {
233 free(vertexes);
234 free(fg);
235 free(bg);
236 free(tex);
239 int zoom_steps, forced_steps;
240 int natural_w, natural_h; // How large our view would be if it wasn't zoomed
242 void zoom(zoom_commands cmd) {
243 pair<int,int> before = compute_zoom(true);
244 int before_steps = zoom_steps;
245 switch (cmd) {
246 case zoom_in: zoom_steps -= init.input.zoom_speed; break;
247 case zoom_out: zoom_steps += init.input.zoom_speed; break;
248 case zoom_reset:
249 zoom_steps = 0;
250 case zoom_resetgrid:
251 compute_forced_zoom();
252 break;
254 pair<int,int> after = compute_zoom(true);
255 if (after == before && (cmd == zoom_in || cmd == zoom_out))
256 zoom_steps = before_steps;
257 else
258 reshape(after);
261 void compute_forced_zoom() {
262 forced_steps = 0;
263 pair<int,int> zoomed = compute_zoom();
264 while (zoomed.first < MIN_GRID_X || zoomed.second < MIN_GRID_Y) {
265 forced_steps++;
266 zoomed = compute_zoom();
268 while (zoomed.first > MAX_GRID_X || zoomed.second > MAX_GRID_Y) {
269 forced_steps--;
270 zoomed = compute_zoom();
274 pair<int,int> compute_zoom(bool clamp = false) {
275 const int dispx = enabler.is_fullscreen() ?
276 init.font.large_font_dispx :
277 init.font.small_font_dispx;
278 const int dispy = enabler.is_fullscreen() ?
279 init.font.large_font_dispy :
280 init.font.small_font_dispy;
281 int w, h;
282 if (dispx < dispy) {
283 w = natural_w + zoom_steps + forced_steps;
284 h = double(natural_h) * (double(w) / double(natural_w));
285 } else {
286 h = natural_h + zoom_steps + forced_steps;
287 w = double(natural_w) * (double(h) / double(natural_h));
289 if (clamp) {
290 w = MIN(MAX(w, MIN_GRID_X), MAX_GRID_X);
291 h = MIN(MAX(h, MIN_GRID_Y), MAX_GRID_Y);
293 return make_pair(w,h);
296 // Parameters: grid units
297 void reshape(pair<int,int> size) {
298 int w = MIN(MAX(size.first, MIN_GRID_X), MAX_GRID_X);
299 int h = MIN(MAX(size.second, MIN_GRID_Y), MAX_GRID_Y);
300 #ifdef DEBUG
301 cout << "Resizing grid to " << w << "x" << h << endl;
302 #endif
303 gps_allocate(w, h);
304 reshape_gl();
307 int off_x, off_y, size_x, size_y;
309 bool get_mouse_coords(int &x, int &y) {
310 int mouse_x, mouse_y;
311 SDL_GetMouseState(&mouse_x, &mouse_y);
312 mouse_x -= off_x; mouse_y -= off_y;
313 if (mouse_x < 0 || mouse_y < 0 ||
314 mouse_x >= size_x || mouse_y >= size_y)
315 return false; // Out of bounds
316 x = double(mouse_x) / double(size_x) * double(gps.dimx);
317 y = double(mouse_y) / double(size_y) * double(gps.dimy);
318 return true;
321 virtual void reshape_gl() {
322 // Allocate array memory
323 allocate(gps.dimx * gps.dimy);
324 // Initialize the vertex array
325 int tile = 0;
326 for (GLfloat x = 0; x < gps.dimx; x++)
327 for (GLfloat y = 0; y < gps.dimy; y++, tile++)
328 write_tile_vertexes(x, y, vertexes + 6*2*tile);
329 // Setup invariant state
330 glEnableClientState(GL_COLOR_ARRAY);
331 /// Set up our coordinate system
332 if (forced_steps + zoom_steps == 0 &&
333 init.display.flag.has_flag(INIT_DISPLAY_FLAG_BLACK_SPACE)) {
334 size_x = gps.dimx * dispx;
335 size_y = gps.dimy * dispy;
336 off_x = (screen->w - size_x) / 2;
337 off_y = (screen->h - size_y) / 2;
338 } else {
339 // If we're zooming (or just not using black space), we use the
340 // entire window.
341 size_x = screen->w;
342 size_y = screen->h;
343 off_x = off_y = 0;
345 glViewport(off_x, off_y, size_x, size_y);
346 glMatrixMode(GL_MODELVIEW);
347 glLoadIdentity();
348 glMatrixMode(GL_PROJECTION);
349 glLoadIdentity();
350 gluOrtho2D(0, gps.dimx, gps.dimy, 0);
353 // Parameters: window size
354 void resize(int w, int h) {
355 // (Re)calculate grid-size
356 dispx = enabler.is_fullscreen() ?
357 init.font.large_font_dispx :
358 init.font.small_font_dispx;
359 dispy = enabler.is_fullscreen() ?
360 init.font.large_font_dispy :
361 init.font.small_font_dispy;
362 natural_w = MAX(w / dispx,1);
363 natural_h = MAX(h / dispy,1);
364 // Compute forced_steps so we satisfy our grid-size limits
365 compute_forced_zoom();
366 // Force a full display cycle
367 gps.force_full_display_count = 1;
368 enabler.flag |= ENABLERFLAG_RENDER;
369 // Reinitialize the video
370 uninit_opengl();
371 init_video(w, h);
372 init_opengl();
373 // Only reshape if we're free to pick grid size
374 if (enabler.overridden_grid_sizes.size() == 0)
375 reshape(compute_zoom());
378 // Parameters: grid size
379 void grid_resize(int w, int h) {
380 reshape(make_pair(w, h));
383 public:
384 void set_fullscreen() {
385 if (enabler.is_fullscreen()) {
386 init.display.desired_windowed_width = screen->w;
387 init.display.desired_windowed_height = screen->h;
388 resize(init.display.desired_fullscreen_width,
389 init.display.desired_fullscreen_height);
390 } else {
391 resize(init.display.desired_windowed_width, init.display.desired_windowed_height);
396 // Specialization for PARTIAL:0
397 class renderer_once : public renderer_opengl {
398 int tile_count;
400 protected:
401 void update_tile(int x, int y) {
402 write_tile_vertexes(x, y, vertexes + tile_count * 6 * 2);
403 write_tile_arrays(x, y,
404 fg + tile_count * 6 * 4,
405 bg + tile_count * 6 * 4,
406 tex + tile_count * 6 * 2);
407 tile_count++;
410 void draw(int dummy) {
411 renderer_opengl::draw(tile_count*6);
412 tile_count = 0;
415 public:
416 renderer_once() {
417 tile_count = 0;
421 // PARTIAL:N
422 class renderer_partial : public renderer_opengl {
423 int buffersz;
424 list<int> erasz; // Previous eras
425 int current_erasz; // And the current one
426 int sum_erasz;
427 int head, tail; // First unused tile, first used tile respectively
428 int redraw_count; // Number of eras to max out at
430 void update_tile(int x, int y) {
431 write_tile_vertexes(x, y, vertexes + head * 6 * 2);
432 write_tile_arrays(x, y,
433 fg + head * 6 * 4,
434 bg + head * 6 * 4,
435 tex + head * 6 * 2);
436 head = (head + 1) % buffersz;
437 current_erasz++; sum_erasz++;
438 if (head == tail) {
439 //gamelog << "Expanding partial-printing buffer" << endl;
440 // Buffer is full, expand it.
441 renderer_opengl::allocate(buffersz * 2);
442 // Move the tail to the end of the newly allocated space
443 tail += buffersz;
444 memmove(vertexes + tail * 6 * 2, vertexes + head * 6 * 2, sizeof(GLfloat) * 6 * 2 * (buffersz - head));
445 memmove(fg + tail * 6 * 4, fg + head * 6 * 4, sizeof(GLfloat) * 6 * 4 * (buffersz - head));
446 memmove(bg + tail * 6 * 4, fg + head * 6 * 4, sizeof(GLfloat) * 6 * 4 * (buffersz - head));
447 memmove(tex + tail * 6 * 2, fg + head * 6 * 2, sizeof(GLfloat) * 6 * 2 * (buffersz - head));
448 // And finish.
449 buffersz *= 2;
453 void allocate(int tile_count) {
454 assert(false);
457 virtual void reshape_gl() {
458 // TODO: This function is duplicate code w/base class reshape_gl
459 // Setup invariant state
460 glEnableClientState(GL_COLOR_ARRAY);
461 /// Set up our coordinate system
462 if (forced_steps + zoom_steps == 0 &&
463 init.display.flag.has_flag(INIT_DISPLAY_FLAG_BLACK_SPACE)) {
464 size_x = gps.dimx * dispx;
465 size_y = gps.dimy * dispy;
466 off_x = (screen->w - size_x) / 2;
467 off_y = (screen->h - size_y) / 2;
468 } else {
469 // If we're zooming (or just not using black space), we use the
470 // entire window.
471 size_x = screen->w;
472 size_y = screen->h;
473 off_x = off_y = 0;
475 glViewport(off_x, off_y, size_x, size_y);
476 glMatrixMode(GL_MODELVIEW);
477 glLoadIdentity();
478 glMatrixMode(GL_PROJECTION);
479 glLoadIdentity();
480 gluOrtho2D(0, gps.dimx, gps.dimy, 0);
483 void draw_arrays(GLfloat *vertexes, GLfloat *fg, GLfloat *bg, GLfloat *tex, int tile_count) {
484 // Set vertex pointer
485 glVertexPointer(2, GL_FLOAT, 0, vertexes);
486 // Render the background colors
487 glDisable(GL_TEXTURE_2D);
488 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
489 glDisable(GL_BLEND);
490 glDisable(GL_ALPHA_TEST);
491 glColorPointer(4, GL_FLOAT, 0, bg);
492 glDrawArrays(GL_TRIANGLES, 0, tile_count * 6);
493 // Render the foreground, colors and textures both
494 glEnable(GL_ALPHA_TEST);
495 glAlphaFunc(GL_NOTEQUAL, 0);
496 glEnable(GL_TEXTURE_2D);
497 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
498 glEnable(GL_BLEND);
499 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
500 glColorPointer(4, GL_FLOAT, 0, fg);
501 glTexCoordPointer(2, GL_FLOAT, 0, tex);
502 glDrawArrays(GL_TRIANGLES, 0, tile_count * 6);
505 void draw(int dummy) {
506 if (tail > head) {
507 // We're straddling the end of the array, so have to do this in two steps
508 draw_arrays(vertexes + tail * 6 * 2,
509 fg + tail * 6 * 4,
510 bg + tail * 6 * 4,
511 tex + tail * 6 * 2,
512 buffersz - tail);
513 draw_arrays(vertexes, fg, bg, tex, head-1);
514 } else {
515 draw_arrays(vertexes + tail * 6 * 2,
516 fg + tail * 6 * 4,
517 bg + tail * 6 * 4,
518 tex + tail * 6 * 2,
519 sum_erasz);
522 printGLError();
523 erasz.push_back(current_erasz); current_erasz = 0;
524 if (erasz.size() == redraw_count) {
525 // Right, time to retire the oldest era.
526 tail = (tail + erasz.front()) % buffersz;
527 sum_erasz -= erasz.front();
528 erasz.pop_front();
532 public:
533 renderer_partial() {
534 redraw_count = init.display.partial_print_count;
535 buffersz = 2048;
536 renderer_opengl::allocate(buffersz);
537 current_erasz = head = tail = sum_erasz = 0;
541 class renderer_accum_buffer : public renderer_once {
542 void draw(int vertex_count) {
543 // Copy the previous frame's buffer back in
544 glAccum(GL_RETURN, 1);
545 renderer_once::draw(vertex_count);
546 // Store the screen contents back to the buffer
547 glAccum(GL_LOAD, 1);
551 class renderer_framebuffer : public renderer_once {
552 GLuint framebuffer, fb_texture;
554 void init_opengl() {
555 glGenFramebuffersEXT(1, &framebuffer);
556 // Allocate FBO texture memory
557 glGenTextures(1, &fb_texture);
558 glBindTexture(GL_TEXTURE_2D, fb_texture);
559 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
560 screen->w, screen->h,
561 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
562 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
563 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
565 // Bind texture to FBO
566 glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, framebuffer);
567 glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
568 GL_TEXTURE_2D, fb_texture, 0);
569 renderer_once::init_opengl();
572 void uninit_opengl() {
573 renderer_once::uninit_opengl();
574 glDeleteTextures(1, &fb_texture);
575 glDeleteFramebuffersEXT(1, &framebuffer);
578 void draw(int vertex_count) {
579 // Bind the framebuffer
580 glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, framebuffer);
581 glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
582 // Draw
583 renderer_once::draw(vertex_count);
584 // Draw the framebuffer to screen
585 glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
586 glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, framebuffer);
587 glBlitFramebufferEXT(0,0, screen->w, screen->h,
588 0,0, screen->w, screen->h,
589 GL_COLOR_BUFFER_BIT, GL_NEAREST);
590 printGLError();
594 class renderer_vbo : public renderer_opengl {
595 GLuint vbo; // Vertexes only
597 void init_opengl() {
598 renderer_opengl::init_opengl();
599 glGenBuffersARB(1, &vbo);
600 glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo);
601 glBufferDataARB(GL_ARRAY_BUFFER_ARB, gps.dimx*gps.dimy*6*2*sizeof(GLfloat), vertexes, GL_STATIC_DRAW_ARB);
602 glVertexPointer(2, GL_FLOAT, 0, 0);
603 glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
606 void uninit_opengl() {
607 glDeleteBuffersARB(1, &vbo);
608 renderer_opengl::uninit_opengl();
611 #endif