3eee8f1187c95f826782edb7fe4f36b006d816b1
[supertux.git] / src / src / video / gl_renderer.cpp
1 //  $Id: gl_renderer.cpp 5063 2007-05-27 11:32:00Z matzeb $
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #ifdef HAVE_OPENGL
22
23 #include <functional>
24 #include <algorithm>
25 #include <cassert>
26 #include <math.h>
27 #include <iostream>
28 #include <SDL_image.h>
29 #include <sstream>
30 #include <iomanip>
31 #include <physfs.h>
32
33 #include "glutil.hpp"
34 #include "gl_renderer.hpp"
35 #include "gl_texture.hpp"
36 #include "gl_surface_data.hpp"
37 #include "drawing_context.hpp"
38 #include "drawing_request.hpp"
39 #include "surface.hpp"
40 #include "font.hpp"
41 #include "main.hpp"
42 #include "gameconfig.hpp"
43 #include "texture.hpp"
44 #include "texture_manager.hpp"
45 #include "obstack/obstackpp.hpp"
46 #define LIGHTMAP_DIV 5
47
48 namespace
49 {
50   inline void intern_draw(float left, float top, float right, float bottom,
51                                   float uv_left, float uv_top,
52                                   float uv_right, float uv_bottom,
53                                   float angle, float alpha,
54                                   const Color& color,
55                                   const Blend& blend,
56                                   DrawingEffect effect)
57   {
58     if(effect & HORIZONTAL_FLIP)
59       std::swap(uv_left, uv_right);
60     if(effect & VERTICAL_FLIP) {
61       std::swap(uv_top, uv_bottom);
62     }
63
64     float center_x = (left + right) / 2;
65     float center_y = (top + bottom) / 2;
66
67     float sa = sinf(angle/180.0f*M_PI);
68     float ca = cosf(angle/180.0f*M_PI);
69
70     left  -= center_x;
71     right -= center_x;
72
73     top    -= center_y;
74     bottom -= center_y;
75
76     glBlendFunc(blend.sfactor, blend.dfactor);
77     glColor4f(color.red, color.green, color.blue, color.alpha * alpha);
78     glBegin(GL_QUADS);
79     glTexCoord2f(uv_left, uv_top);
80     glVertex2f(left*ca - top*sa + center_x,
81                left*sa + top*ca + center_y);
82
83     glTexCoord2f(uv_right, uv_top);
84     glVertex2f(right*ca - top*sa + center_x,
85                right*sa + top*ca + center_y);
86
87     glTexCoord2f(uv_right, uv_bottom);
88     glVertex2f(right*ca - bottom*sa + center_x,
89                right*sa + bottom*ca + center_y);
90
91     glTexCoord2f(uv_left, uv_bottom);
92     glVertex2f(left*ca - bottom*sa + center_x,
93                left*sa + bottom*ca + center_y);
94     glEnd();
95
96     // FIXME: find a better way to restore the blend mode
97     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
98     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
99   }
100 }
101
102 namespace GL
103 {
104   Renderer::Renderer()
105   {
106     if(texture_manager != 0)
107       texture_manager->save_textures();
108
109     if(config->try_vsync) {
110       /* we want vsync for smooth scrolling */
111     SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
112     }
113
114     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
115     SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
116     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
117     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
118
119     int flags = SDL_OPENGL;
120     if(config->use_fullscreen)
121       flags |= SDL_FULLSCREEN;
122     int width = config->screenwidth;
123     int height = config->screenheight;
124     int bpp = 0;
125
126     SDL_Surface *screen = SDL_SetVideoMode(width, height, bpp, flags);
127     if(screen == 0) {
128       std::stringstream msg;
129       msg << "Couldn't set video mode (" << width << "x" << height
130           << "-" << bpp << "bpp): " << SDL_GetError();
131       throw std::runtime_error(msg.str());
132     }
133
134     // setup opengl state and transform
135     glDisable(GL_DEPTH_TEST);
136     glDisable(GL_CULL_FACE);
137     glEnable(GL_TEXTURE_2D);
138     glEnable(GL_BLEND);
139     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
140
141     glViewport(0, 0, screen->w, screen->h);
142     glMatrixMode(GL_PROJECTION);
143     glLoadIdentity();
144     // logical resolution here not real monitor resolution
145     glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1.0, 1.0);
146     glMatrixMode(GL_MODELVIEW);
147     glLoadIdentity();
148     glTranslatef(0, 0, 0);
149
150     check_gl_error("Setting up view matrices");
151
152
153     if(texture_manager == 0)
154       texture_manager = new TextureManager();
155     else
156       texture_manager->reload_textures();
157   }
158
159   Renderer::~Renderer()
160   {
161   }
162
163   void
164   Renderer::draw_surface(const DrawingRequest& request)
165   {
166     const Surface* surface = (const Surface*) request.request_data;
167     GL::Texture *gltexture = dynamic_cast<GL::Texture *>(surface->get_texture());
168     GL::SurfaceData *surface_data = reinterpret_cast<GL::SurfaceData *>(surface->get_surface_data());
169
170     glBindTexture(GL_TEXTURE_2D, gltexture->get_handle());
171     intern_draw(request.pos.x, request.pos.y,
172                 request.pos.x + surface->get_width(),
173                 request.pos.y + surface->get_height(),
174                 surface_data->get_uv_left(),
175                 surface_data->get_uv_top(),
176                 surface_data->get_uv_right(),
177                 surface_data->get_uv_bottom(),
178                 request.angle,
179                 request.alpha,
180                 request.color,
181                 request.blend,
182                 request.drawing_effect);
183   }
184
185   void
186   Renderer::draw_surface_part(const DrawingRequest& request)
187   {
188     const SurfacePartRequest* surfacepartrequest
189       = (SurfacePartRequest*) request.request_data;
190     const Surface *surface = surfacepartrequest->surface;
191     GL::Texture *gltexture = dynamic_cast<GL::Texture *>(surface->get_texture());
192     GL::SurfaceData *surface_data = reinterpret_cast<GL::SurfaceData *>(surface->get_surface_data());
193
194     float uv_width = surface_data->get_uv_right() - surface_data->get_uv_left();
195     float uv_height = surface_data->get_uv_bottom() - surface_data->get_uv_top();
196
197     float uv_left = surface_data->get_uv_left() + (uv_width * surfacepartrequest->source.x) / surface->get_width();
198     float uv_top = surface_data->get_uv_top() + (uv_height * surfacepartrequest->source.y) / surface->get_height();
199     float uv_right = surface_data->get_uv_left() + (uv_width * (surfacepartrequest->source.x + surfacepartrequest->size.x)) / surface->get_width();
200     float uv_bottom = surface_data->get_uv_top() + (uv_height * (surfacepartrequest->source.y + surfacepartrequest->size.y)) / surface->get_height();
201
202     glBindTexture(GL_TEXTURE_2D, gltexture->get_handle());
203     intern_draw(request.pos.x, request.pos.y,
204                 request.pos.x + surfacepartrequest->size.x,
205                 request.pos.y + surfacepartrequest->size.y,
206                 uv_left,
207                 uv_top,
208                 uv_right,
209                 uv_bottom,
210                 0.0,
211                 request.alpha,
212                 Color(1.0, 1.0, 1.0),
213                 Blend(),
214                 request.drawing_effect);
215   }
216
217   void
218   Renderer::draw_gradient(const DrawingRequest& request)
219   {
220     const GradientRequest* gradientrequest 
221       = (GradientRequest*) request.request_data;
222     const Color& top = gradientrequest->top;
223     const Color& bottom = gradientrequest->bottom;
224
225     glDisable(GL_TEXTURE_2D);
226     glBegin(GL_QUADS);
227     glColor4f(top.red, top.green, top.blue, top.alpha);
228     glVertex2f(0, 0);
229     glVertex2f(SCREEN_WIDTH, 0);
230     glColor4f(bottom.red, bottom.green, bottom.blue, bottom.alpha);
231     glVertex2f(SCREEN_WIDTH, SCREEN_HEIGHT);
232     glVertex2f(0, SCREEN_HEIGHT);
233     glEnd();
234     glEnable(GL_TEXTURE_2D);
235     glColor4f(1, 1, 1, 1);
236   }
237
238   void
239   Renderer::draw_filled_rect(const DrawingRequest& request)
240   {
241     const FillRectRequest* fillrectrequest
242       = (FillRectRequest*) request.request_data;
243
244     float x = request.pos.x;
245     float y = request.pos.y;
246     float w = fillrectrequest->size.x;
247     float h = fillrectrequest->size.y;
248
249     glDisable(GL_TEXTURE_2D);
250     glColor4f(fillrectrequest->color.red, fillrectrequest->color.green,
251               fillrectrequest->color.blue, fillrectrequest->color.alpha);
252
253     glBegin(GL_QUADS);
254     glVertex2f(x, y);
255     glVertex2f(x+w, y);
256     glVertex2f(x+w, y+h);
257     glVertex2f(x, y+h);
258     glEnd();
259     glEnable(GL_TEXTURE_2D);
260     glColor4f(1, 1, 1, 1);
261   }
262
263   void 
264   Renderer::do_take_screenshot()
265   {
266     // [Christoph] TODO: Yes, this method also takes care of the actual disk I/O. Split it?
267
268     SDL_Surface *shot_surf;
269     // create surface to hold screenshot
270     #if SDL_BYTEORDER == SDL_BIG_ENDIAN
271     shot_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, SCREEN_WIDTH, SCREEN_HEIGHT, 24, 0x00FF0000, 0x0000FF00, 0x000000FF, 0);
272     #else
273     shot_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, SCREEN_WIDTH, SCREEN_HEIGHT, 24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0);
274     #endif
275     if (!shot_surf) {
276       log_warning << "Could not create RGB Surface to contain screenshot" << std::endl;
277       return;
278     }
279
280     // read pixels into array
281     char* pixels = new char[3 * SCREEN_WIDTH * SCREEN_HEIGHT];
282     if (!pixels) {
283       log_warning << "Could not allocate memory to store screenshot" << std::endl;
284       SDL_FreeSurface(shot_surf);
285       return;
286     }
287     glReadPixels(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels);
288
289     // copy array line-by-line
290     for (int i = 0; i < SCREEN_HEIGHT; i++) {
291       char* src = pixels + (3 * SCREEN_WIDTH * (SCREEN_HEIGHT - i - 1));
292       if(SDL_MUSTLOCK(shot_surf))
293       {
294         SDL_LockSurface(shot_surf);
295       }
296       char* dst = ((char*)shot_surf->pixels) + i * shot_surf->pitch;
297       memcpy(dst, src, 3 * SCREEN_WIDTH);
298       if(SDL_MUSTLOCK(shot_surf))
299       {
300         SDL_UnlockSurface(shot_surf);
301       }
302     }
303
304     // free array
305     delete[](pixels);
306
307     // save screenshot
308     static const std::string writeDir = PHYSFS_getWriteDir();
309     static const std::string dirSep = PHYSFS_getDirSeparator();
310     static const std::string baseName = "screenshot";
311     static const std::string fileExt = ".bmp";
312     std::string fullFilename;
313     for (int num = 0; num < 1000; num++) {
314       std::ostringstream oss;
315       oss << baseName;
316       oss << std::setw(3) << std::setfill('0') << num;
317       oss << fileExt;
318       std::string fileName = oss.str();
319       fullFilename = writeDir + dirSep + fileName;
320       if (!PHYSFS_exists(fileName.c_str())) {
321         SDL_SaveBMP(shot_surf, fullFilename.c_str());
322         log_debug << "Wrote screenshot to \"" << fullFilename << "\"" << std::endl;
323         SDL_FreeSurface(shot_surf);
324         return;
325       }
326     }
327     log_warning << "Did not save screenshot, because all files up to \"" << fullFilename << "\" already existed" << std::endl;
328     SDL_FreeSurface(shot_surf);
329   }
330
331   void
332   Renderer::flip()
333   {
334     assert_gl("drawing");
335     SDL_GL_SwapBuffers();
336   }
337 }
338
339 #endif