3 class renderer_opengl
: public renderer
{
5 virtual bool uses_opengl() { return true; }
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
;
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
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;
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.
48 // Set the viewport and clear
49 glViewport(0, 0, screen
->w
, screen
->h
);
50 glClear(GL_COLOR_BUFFER_BIT
);
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
61 vertex
[2] = x
+1; // Upper right
63 vertex
[4] = x
; // Lower left
65 vertex
[6] = x
; // Lower left again (triangle 2)
67 vertex
[8] = x
+1; // Upper right
69 vertex
[10] = x
+1; // Lower right
73 virtual void allocate(int tiles
) {
74 vertexes
= static_cast<GLfloat
*>(realloc(vertexes
, sizeof(GLfloat
) * tiles
* 2 * 6));
76 fg
= static_cast<GLfloat
*>(realloc(fg
, sizeof(GLfloat
) * tiles
* 4 * 6));
78 bg
= static_cast<GLfloat
*>(realloc(bg
, sizeof(GLfloat
) * tiles
* 4 * 6));
80 tex
= static_cast<GLfloat
*>(realloc(tex
, sizeof(GLfloat
) * tiles
* 2 * 6));
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
);
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
);
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
);
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
++) {
129 *(bg
++) = id
.left
.br
;
130 *(bg
++) = id
.left
.bg
;
131 *(bg
++) = id
.left
.bb
;
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
;
154 void update_tile(int x
, int y
) {
155 const int tile
= x
*gps
.dimy
+ y
;
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
);
164 glClear(GL_COLOR_BUFFER_BIT
);
165 for (int x
= 0; x
< gps
.dimx
; x
++)
166 for (int y
= 0; y
< gps
.dimy
; y
++)
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();
180 // Init member variables so realloc'll work
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");
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
224 report_error("SDL initialization failure", SDL_GetError());
232 virtual ~renderer_opengl() {
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
;
246 case zoom_in
: zoom_steps
-= init
.input
.zoom_speed
; break;
247 case zoom_out
: zoom_steps
+= init
.input
.zoom_speed
; break;
251 compute_forced_zoom();
254 pair
<int,int> after
= compute_zoom(true);
255 if (after
== before
&& (cmd
== zoom_in
|| cmd
== zoom_out
))
256 zoom_steps
= before_steps
;
261 void compute_forced_zoom() {
263 pair
<int,int> zoomed
= compute_zoom();
264 while (zoomed
.first
< MIN_GRID_X
|| zoomed
.second
< MIN_GRID_Y
) {
266 zoomed
= compute_zoom();
268 while (zoomed
.first
> MAX_GRID_X
|| zoomed
.second
> MAX_GRID_Y
) {
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
;
283 w
= natural_w
+ zoom_steps
+ forced_steps
;
284 h
= double(natural_h
) * (double(w
) / double(natural_w
));
286 h
= natural_h
+ zoom_steps
+ forced_steps
;
287 w
= double(natural_w
) * (double(h
) / double(natural_h
));
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
);
301 cout
<< "Resizing grid to " << w
<< "x" << h
<< endl
;
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
);
321 virtual void reshape_gl() {
322 // Allocate array memory
323 allocate(gps
.dimx
* gps
.dimy
);
324 // Initialize the vertex array
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;
339 // If we're zooming (or just not using black space), we use the
345 glViewport(off_x
, off_y
, size_x
, size_y
);
346 glMatrixMode(GL_MODELVIEW
);
348 glMatrixMode(GL_PROJECTION
);
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
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
));
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
);
391 resize(init
.display
.desired_windowed_width
, init
.display
.desired_windowed_height
);
396 // Specialization for PARTIAL:0
397 class renderer_once
: public renderer_opengl
{
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);
410 void draw(int dummy
) {
411 renderer_opengl::draw(tile_count
*6);
422 class renderer_partial
: public renderer_opengl
{
424 list
<int> erasz
; // Previous eras
425 int current_erasz
; // And the current one
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
,
436 head
= (head
+ 1) % buffersz
;
437 current_erasz
++; sum_erasz
++;
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
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
));
453 void allocate(int tile_count
) {
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;
469 // If we're zooming (or just not using black space), we use the
475 glViewport(off_x
, off_y
, size_x
, size_y
);
476 glMatrixMode(GL_MODELVIEW
);
478 glMatrixMode(GL_PROJECTION
);
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
);
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
);
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
) {
507 // We're straddling the end of the array, so have to do this in two steps
508 draw_arrays(vertexes
+ tail
* 6 * 2,
513 draw_arrays(vertexes
, fg
, bg
, tex
, head
-1);
515 draw_arrays(vertexes
+ tail
* 6 * 2,
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();
534 redraw_count
= init
.display
.partial_print_count
;
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
551 class renderer_framebuffer
: public renderer_once
{
552 GLuint framebuffer
, fb_texture
;
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);
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
);
594 class renderer_vbo
: public renderer_opengl
{
595 GLuint vbo
; // Vertexes only
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();