Fixed missing SDL_WINDOW_RESIZABLE flag in GLRenderer
[supertux.git] / src / video / gl / gl_renderer.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //      Updated by GiBy 2013 for SDL2 <giby_the_kid@yahoo.fr>
4 //
5 //  This program is free software: you can redistribute it and/or modify
6 //  it under the terms of the GNU General Public License as published by
7 //  the Free Software Foundation, either version 3 of the License, or
8 //  (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 #include "video/gl/gl_renderer.hpp"
19
20 #include <iomanip>
21 #include <iostream>
22 #include <physfs.h>
23 #include "SDL.h"
24
25 #include "supertux/gameconfig.hpp"
26 #include "supertux/globals.hpp"
27 #include "video/drawing_request.hpp"
28 #include "video/gl/gl_surface_data.hpp"
29 #include "video/gl/gl_texture.hpp"
30 #include "video/util.hpp"
31
32 #define LIGHTMAP_DIV 5
33
34 #ifdef GL_VERSION_ES_CM_1_0
35 #  define glOrtho glOrthof
36 #endif
37
38 GLRenderer::GLRenderer() :
39   window(),
40   desktop_size(0, 0),
41   fullscreen_active(false),
42   last_texture(static_cast<GLuint> (-1))
43 {
44   Renderer::instance_ = this;
45
46   SDL_DisplayMode mode;
47   SDL_GetCurrentDisplayMode(0, &mode);
48   desktop_size = Size(mode.w, mode.h);
49
50   if(texture_manager != 0)
51     texture_manager->save_textures();
52
53   if(g_config->try_vsync) {
54     /* we want vsync for smooth scrolling */
55     if (SDL_GL_SetSwapInterval(-1) != 0)
56     {
57       log_info << "no support for late swap tearing vsync: " << SDL_GetError() << std::endl;
58       if (SDL_GL_SetSwapInterval(1))
59       {
60         log_info << "no support for vsync: " << SDL_GetError() << std::endl;
61       }
62     }
63   }
64
65   SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
66
67   SDL_GL_SetAttribute(SDL_GL_RED_SIZE,   5);
68   SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
69   SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,  5);
70
71   apply_video_mode();
72
73   // setup opengl state and transform
74   glDisable(GL_DEPTH_TEST);
75   glDisable(GL_CULL_FACE);
76   glEnable(GL_TEXTURE_2D);
77   glEnable(GL_BLEND);
78   glEnableClientState(GL_VERTEX_ARRAY);
79   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
80   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
81
82   // Init the projection matrix, viewport and stuff
83   apply_config();
84
85   if(texture_manager == 0)
86     texture_manager = new TextureManager();
87   else
88     texture_manager->reload_textures();
89
90 #ifndef GL_VERSION_ES_CM_1_0
91   GLenum err = glewInit();
92   if (GLEW_OK != err)
93   {
94     std::ostringstream out;
95     out << "GLRenderer: " << glewGetErrorString(err);
96     throw std::runtime_error(out.str());
97   }
98   log_info << "Using GLEW " << glewGetString(GLEW_VERSION) << std::endl;
99   log_info << "GLEW_ARB_texture_non_power_of_two: " << static_cast<int>(GLEW_ARB_texture_non_power_of_two) << std::endl;
100 #endif
101 }
102
103 GLRenderer::~GLRenderer()
104 {
105   SDL_GL_DeleteContext(glcontext);
106   SDL_DestroyWindow(window);
107 }
108
109 void
110 GLRenderer::draw_surface(const DrawingRequest& request)
111 {
112   const Surface* surface = (const Surface*) request.request_data;
113   if(surface == NULL)
114   {
115     return;
116   }
117   GLTexture* gltexture = static_cast<GLTexture*>(surface->get_texture().get());
118   if(gltexture == NULL)
119   {
120     return;
121   }
122   GLSurfaceData *surface_data = static_cast<GLSurfaceData*>(surface->get_surface_data());
123   if(surface_data == NULL)
124   {
125     return;
126   }
127
128   GLuint th = gltexture->get_handle();
129   if (th != last_texture) {
130     last_texture = th;
131     glBindTexture(GL_TEXTURE_2D, th);
132   }
133   intern_draw(request.pos.x, request.pos.y,
134               request.pos.x + surface->get_width(),
135               request.pos.y + surface->get_height(),
136               surface_data->get_uv_left(),
137               surface_data->get_uv_top(),
138               surface_data->get_uv_right(),
139               surface_data->get_uv_bottom(),
140               request.angle,
141               request.alpha,
142               request.color,
143               request.blend,
144               request.drawing_effect);
145 }
146
147 void
148 GLRenderer::draw_surface_part(const DrawingRequest& request)
149 {
150   const SurfacePartRequest* surfacepartrequest
151     = (SurfacePartRequest*) request.request_data;
152   const Surface* surface = surfacepartrequest->surface;
153   boost::shared_ptr<GLTexture> gltexture = boost::dynamic_pointer_cast<GLTexture>(surface->get_texture());
154   GLSurfaceData *surface_data = reinterpret_cast<GLSurfaceData *>(surface->get_surface_data());
155
156   float uv_width = surface_data->get_uv_right() - surface_data->get_uv_left();
157   float uv_height = surface_data->get_uv_bottom() - surface_data->get_uv_top();
158
159   float uv_left = surface_data->get_uv_left() + (uv_width * surfacepartrequest->source.x) / surface->get_width();
160   float uv_top = surface_data->get_uv_top() + (uv_height * surfacepartrequest->source.y) / surface->get_height();
161   float uv_right = surface_data->get_uv_left() + (uv_width * (surfacepartrequest->source.x + surfacepartrequest->size.x)) / surface->get_width();
162   float uv_bottom = surface_data->get_uv_top() + (uv_height * (surfacepartrequest->source.y + surfacepartrequest->size.y)) / surface->get_height();
163
164   GLuint th = gltexture->get_handle();
165   if (th != last_texture) {
166     last_texture = th;
167     glBindTexture(GL_TEXTURE_2D, th);
168   }
169   intern_draw(request.pos.x, request.pos.y,
170               request.pos.x + surfacepartrequest->size.x,
171               request.pos.y + surfacepartrequest->size.y,
172               uv_left,
173               uv_top,
174               uv_right,
175               uv_bottom,
176               0.0,
177               request.alpha,
178               request.color,
179               Blend(),
180               request.drawing_effect);
181 }
182
183 void
184 GLRenderer::draw_gradient(const DrawingRequest& request)
185 {
186   const GradientRequest* gradientrequest
187     = (GradientRequest*) request.request_data;
188   const Color& top = gradientrequest->top;
189   const Color& bottom = gradientrequest->bottom;
190
191   glDisable(GL_TEXTURE_2D);
192   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
193   glEnableClientState(GL_COLOR_ARRAY);
194
195   float vertices[] = {
196     0, 0,
197     float(SCREEN_WIDTH), 0,
198     float(SCREEN_WIDTH), float(SCREEN_HEIGHT),
199     0, float(SCREEN_HEIGHT)
200   };
201   glVertexPointer(2, GL_FLOAT, 0, vertices);
202
203   float colors[] = {
204     top.red, top.green, top.blue, top.alpha,
205     top.red, top.green, top.blue, top.alpha,
206     bottom.red, bottom.green, bottom.blue, bottom.alpha,
207     bottom.red, bottom.green, bottom.blue, bottom.alpha,
208   };
209   glColorPointer(4, GL_FLOAT, 0, colors);
210
211   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
212
213   glDisableClientState(GL_COLOR_ARRAY);
214   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
215
216   glEnable(GL_TEXTURE_2D);
217   glColor4f(1, 1, 1, 1);
218 }
219
220 void
221 GLRenderer::draw_filled_rect(const DrawingRequest& request)
222 {
223   const FillRectRequest* fillrectrequest
224     = (FillRectRequest*) request.request_data;
225
226   glDisable(GL_TEXTURE_2D);
227   glColor4f(fillrectrequest->color.red, fillrectrequest->color.green,
228             fillrectrequest->color.blue, fillrectrequest->color.alpha);
229   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
230
231   if (fillrectrequest->radius != 0.0f)
232   {
233     // draw round rect
234     // Keep radius in the limits, so that we get a circle instead of
235     // just graphic junk
236     float radius = std::min(fillrectrequest->radius,
237                             std::min(fillrectrequest->size.x/2,
238                                      fillrectrequest->size.y/2));
239
240     // inner rectangle
241     Rectf irect(request.pos.x    + radius,
242                 request.pos.y    + radius,
243                 request.pos.x + fillrectrequest->size.x - radius,
244                 request.pos.y + fillrectrequest->size.y - radius);
245
246     int n = 8;
247     int p = 0;
248     std::vector<float> vertices((n+1) * 4 * 2);
249
250     for(int i = 0; i <= n; ++i)
251     {
252       float x = sinf(i * (M_PI/2) / n) * radius;
253       float y = cosf(i * (M_PI/2) / n) * radius;
254
255       vertices[p++] = irect.get_left() - x;
256       vertices[p++] = irect.get_top()  - y;
257
258       vertices[p++] = irect.get_right() + x;
259       vertices[p++] = irect.get_top()   - y;
260     }
261
262     for(int i = 0; i <= n; ++i)
263     {
264       float x = cosf(i * (M_PI/2) / n) * radius;
265       float y = sinf(i * (M_PI/2) / n) * radius;
266
267       vertices[p++] = irect.get_left()   - x;
268       vertices[p++] = irect.get_bottom() + y;
269
270       vertices[p++] = irect.get_right()  + x;
271       vertices[p++] = irect.get_bottom() + y;
272     }
273
274     glVertexPointer(2, GL_FLOAT, 0, &*vertices.begin());
275     glDrawArrays(GL_TRIANGLE_STRIP, 0,  vertices.size()/2);
276   }
277   else
278   {
279     float x = request.pos.x;
280     float y = request.pos.y;
281     float w = fillrectrequest->size.x;
282     float h = fillrectrequest->size.y;
283
284     float vertices[] = {
285       x,   y,
286       x+w, y,
287       x+w, y+h,
288       x,   y+h
289     };
290     glVertexPointer(2, GL_FLOAT, 0, vertices);
291
292     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
293   }
294
295   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
296   glEnable(GL_TEXTURE_2D);
297   glColor4f(1, 1, 1, 1);
298 }
299
300 void
301 GLRenderer::draw_inverse_ellipse(const DrawingRequest& request)
302 {
303   const InverseEllipseRequest* ellipse = (InverseEllipseRequest*)request.request_data;
304
305   glDisable(GL_TEXTURE_2D);
306   glColor4f(ellipse->color.red,  ellipse->color.green,
307             ellipse->color.blue, ellipse->color.alpha);
308
309   float x = request.pos.x;
310   float y = request.pos.y;
311   float w = ellipse->size.x/2.0f;
312   float h = ellipse->size.y/2.0f;
313
314   static const int slices = 16;
315   static const int points = (slices+1) * 12;
316
317   float vertices[points * 2];
318   int   p = 0;
319
320   // Bottom
321   vertices[p++] = SCREEN_WIDTH; vertices[p++] = SCREEN_HEIGHT;
322   vertices[p++] = 0;            vertices[p++] = SCREEN_HEIGHT;
323   vertices[p++] = x;            vertices[p++] = y+h;
324
325   // Top
326   vertices[p++] = SCREEN_WIDTH; vertices[p++] = 0;
327   vertices[p++] = 0;            vertices[p++] = 0;
328   vertices[p++] = x;            vertices[p++] = y-h;
329
330   // Left
331   vertices[p++] = SCREEN_WIDTH; vertices[p++] = 0;
332   vertices[p++] = SCREEN_WIDTH; vertices[p++] = SCREEN_HEIGHT;
333   vertices[p++] = x+w;          vertices[p++] = y;
334
335   // Right
336   vertices[p++] = 0;            vertices[p++] = 0;
337   vertices[p++] = 0;            vertices[p++] = SCREEN_HEIGHT;
338   vertices[p++] = x-w;          vertices[p++] = y;
339
340   for(int i = 0; i < slices; ++i)
341   {
342     float ex1 = sinf(M_PI/2 / slices * i) * w;
343     float ey1 = cosf(M_PI/2 / slices * i) * h;
344
345     float ex2 = sinf(M_PI/2 / slices * (i+1)) * w;
346     float ey2 = cosf(M_PI/2 / slices * (i+1)) * h;
347
348     // Bottom/Right
349     vertices[p++] = SCREEN_WIDTH; vertices[p++] = SCREEN_HEIGHT;
350     vertices[p++] = x + ex1;      vertices[p++] = y + ey1;
351     vertices[p++] = x + ex2;      vertices[p++] = y + ey2;
352
353     // Top/Left
354     vertices[p++] = 0;            vertices[p++] = 0;
355     vertices[p++] = x - ex1;      vertices[p++] = y - ey1;
356     vertices[p++] = x - ex2;      vertices[p++] = y - ey2;
357
358     // Top/Right
359     vertices[p++] = SCREEN_WIDTH; vertices[p++] = 0;
360     vertices[p++] = x + ex1;      vertices[p++] = y - ey1;
361     vertices[p++] = x + ex2;      vertices[p++] = y - ey2;
362
363     // Bottom/Left
364     vertices[p++] = 0;            vertices[p++] = SCREEN_HEIGHT;
365     vertices[p++] = x - ex1;      vertices[p++] = y + ey1;
366     vertices[p++] = x - ex2;      vertices[p++] = y + ey2;
367   }
368
369   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
370   glVertexPointer(2, GL_FLOAT, 0, vertices);
371
372   glDrawArrays(GL_TRIANGLES, 0, points);
373
374   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
375
376   glEnable(GL_TEXTURE_2D);
377   glColor4f(1, 1, 1, 1);
378 }
379
380 void
381 GLRenderer::do_take_screenshot()
382 {
383   // [Christoph] TODO: Yes, this method also takes care of the actual disk I/O. Split it?
384
385   SDL_Surface *shot_surf;
386   // create surface to hold screenshot
387 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
388   shot_surf = SDL_CreateRGBSurface(0, SCREEN_WIDTH, SCREEN_HEIGHT, 24, 0x00FF0000, 0x0000FF00, 0x000000FF, 0);
389 #else
390   shot_surf = SDL_CreateRGBSurface(0, SCREEN_WIDTH, SCREEN_HEIGHT, 24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0);
391 #endif
392   if (!shot_surf) {
393     log_warning << "Could not create RGB Surface to contain screenshot" << std::endl;
394     return;
395   }
396
397   // read pixels into array
398   char* pixels = new char[3 * SCREEN_WIDTH * SCREEN_HEIGHT];
399   if (!pixels) {
400     log_warning << "Could not allocate memory to store screenshot" << std::endl;
401     SDL_FreeSurface(shot_surf);
402     return;
403   }
404   glPixelStorei(GL_PACK_ALIGNMENT, 1);
405   glReadPixels(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels);
406
407   // copy array line-by-line
408   for (int i = 0; i < SCREEN_HEIGHT; i++) {
409     char* src = pixels + (3 * SCREEN_WIDTH * (SCREEN_HEIGHT - i - 1));
410     if(SDL_MUSTLOCK(shot_surf))
411     {
412       SDL_LockSurface(shot_surf);
413     }
414     char* dst = ((char*)shot_surf->pixels) + i * shot_surf->pitch;
415     memcpy(dst, src, 3 * SCREEN_WIDTH);
416     if(SDL_MUSTLOCK(shot_surf))
417     {
418       SDL_UnlockSurface(shot_surf);
419     }
420   }
421
422   // free array
423   delete[](pixels);
424
425   // save screenshot
426   static const std::string writeDir = PHYSFS_getWriteDir();
427   static const std::string dirSep = PHYSFS_getDirSeparator();
428   static const std::string baseName = "screenshot";
429   static const std::string fileExt = ".bmp";
430   std::string fullFilename;
431   for (int num = 0; num < 1000; num++) {
432     std::ostringstream oss;
433     oss << baseName;
434     oss << std::setw(3) << std::setfill('0') << num;
435     oss << fileExt;
436     std::string fileName = oss.str();
437     fullFilename = writeDir + dirSep + fileName;
438     if (!PHYSFS_exists(fileName.c_str())) {
439       SDL_SaveBMP(shot_surf, fullFilename.c_str());
440       log_debug << "Wrote screenshot to \"" << fullFilename << "\"" << std::endl;
441       SDL_FreeSurface(shot_surf);
442       return;
443     }
444   }
445   log_warning << "Did not save screenshot, because all files up to \"" << fullFilename << "\" already existed" << std::endl;
446   SDL_FreeSurface(shot_surf);
447 }
448
449 void
450 GLRenderer::flip()
451 {
452   assert_gl("drawing");
453   SDL_GL_SwapWindow(window);
454 }
455
456 void
457 GLRenderer::resize(int w, int h)
458 {
459   g_config->window_size = Size(w, h);
460
461   apply_config();
462 }
463
464 void
465 GLRenderer::apply_config()
466 {
467   apply_video_mode();
468
469   Size target_size = g_config->use_fullscreen ?
470     ((g_config->fullscreen_size == Size(0, 0)) ? desktop_size : g_config->fullscreen_size) :
471     g_config->window_size;
472
473   float pixel_aspect_ratio = 1.0f;
474   if (g_config->aspect_size != Size(0, 0))
475   {
476     pixel_aspect_ratio = calculate_pixel_aspect_ratio(desktop_size,
477                                                       g_config->aspect_size);
478   }
479   else if (g_config->use_fullscreen)
480   {
481     pixel_aspect_ratio = calculate_pixel_aspect_ratio(desktop_size,
482                                                       target_size);
483   }
484
485   Size max_size(1280, 800);
486   Size min_size(640, 480);
487
488   Vector scale;
489   Size logical_size;
490   calculate_viewport(min_size, max_size, target_size,
491                      pixel_aspect_ratio,
492                      g_config->magnification,
493                      scale,
494                      logical_size,
495                      viewport);
496
497   SCREEN_WIDTH = logical_size.width;
498   SCREEN_HEIGHT = logical_size.height;
499
500   if (viewport.x != 0 || viewport.y != 0)
501   {
502     // Clear both buffers so that we get a clean black border without junk
503     glClear(GL_COLOR_BUFFER_BIT);
504     SDL_GL_SwapWindow(window);
505     glClear(GL_COLOR_BUFFER_BIT);
506     SDL_GL_SwapWindow(window);
507   }
508
509   glViewport(viewport.x, viewport.y, viewport.w, viewport.h);
510
511   glMatrixMode(GL_PROJECTION);
512   glLoadIdentity();
513
514   glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1, 1);
515
516   glMatrixMode(GL_MODELVIEW);
517   glLoadIdentity();
518   glTranslatef(0, 0, 0);
519   check_gl_error("Setting up view matrices");
520 }
521
522 void
523 GLRenderer::apply_video_mode()
524 {
525   if (window)
526   {
527     if (!g_config->use_fullscreen)
528     {
529       SDL_SetWindowFullscreen(window, 0);
530     }
531     else
532     {
533       if (g_config->fullscreen_size.width == 0 &&
534           g_config->fullscreen_size.height == 0)
535       {
536         if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP) != 0)
537         {
538           log_warning << "failed to switch to desktop fullscreen mode: "
539                       << SDL_GetError() << std::endl;
540         }
541         else
542         {
543           log_info << "switched to desktop fullscreen mode" << std::endl;
544         }
545       }
546       else
547       {
548         SDL_DisplayMode mode;
549         mode.format = SDL_PIXELFORMAT_RGB888;
550         mode.w = g_config->fullscreen_size.width;
551         mode.h = g_config->fullscreen_size.height;
552         mode.refresh_rate = g_config->fullscreen_refresh_rate;
553         mode.driverdata = 0;
554
555         if (SDL_SetWindowDisplayMode(window, &mode) != 0)
556         {
557           log_warning << "failed to set display mode: "
558                       << mode.w << "x" << mode.h << "@" << mode.refresh_rate << ": "
559                       << SDL_GetError() << std::endl;
560         }
561         else
562         {
563           if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0)
564           {
565             log_warning << "failed to switch to fullscreen mode: "
566                         << mode.w << "x" << mode.h << "@" << mode.refresh_rate << ": "
567                         << SDL_GetError() << std::endl;
568           }
569           else
570           {
571             log_info << "switched to fullscreen mode: "
572                      << mode.w << "x" << mode.h << "@" << mode.refresh_rate << std::endl;
573           }
574         }
575       }
576     }
577   }
578   else
579   {
580     int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
581     Size size;
582     if (g_config->use_fullscreen)
583     {
584       if (g_config->fullscreen_size == Size(0, 0))
585       {
586         flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
587         size = desktop_size;
588       }
589       else
590       {
591         flags |= SDL_WINDOW_FULLSCREEN;
592         size.width  = g_config->fullscreen_size.width;
593         size.height = g_config->fullscreen_size.height;
594       }
595     }
596     else
597     {
598       size = g_config->window_size;
599     }
600
601     window = SDL_CreateWindow("SuperTux",
602                               SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
603                               size.width, size.height,
604                               flags);
605     if (!window)
606     {
607       std::ostringstream msg;
608       msg << "Couldn't set video mode " << size.width << "x" << size.height << ": " << SDL_GetError();
609       throw std::runtime_error(msg.str());
610     }
611     else
612     {
613       glcontext = SDL_GL_CreateContext(window);
614
615       SCREEN_WIDTH = size.width;
616       SCREEN_HEIGHT = size.height;
617
618       fullscreen_active = g_config->use_fullscreen;
619     }
620   }
621 }
622
623 Vector
624 GLRenderer::to_logical(int physical_x, int physical_y)
625 {
626   return Vector(static_cast<float>(physical_x - viewport.x) * SCREEN_WIDTH / viewport.w,
627                 static_cast<float>(physical_y - viewport.y) * SCREEN_HEIGHT / viewport.h);
628 }
629
630 void
631 GLRenderer::set_gamma(float gamma)
632 {
633   Uint16 ramp[256];
634   SDL_CalculateGammaRamp(gamma, ramp);
635   SDL_SetWindowGammaRamp(window, ramp, ramp, ramp);
636 }
637
638 /* EOF */