Refactored video/ subsystem to make adding other methods of rendering (in particular...
[supertux.git] / src / video / sdl_renderer.cpp
1 //  $Id: sdl_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 #include <functional>
22 #include <algorithm>
23 #include <stdexcept>
24 #include <cassert>
25 #include <iostream>
26 #include <SDL_image.h>
27 #include <sstream>
28 #include <iomanip>
29 #include <physfs.h>
30
31 #include "glutil.hpp"
32 #include "sdl_renderer.hpp"
33 #include "sdl_texture.hpp"
34 #include "drawing_context.hpp"
35 #include "drawing_request.hpp"
36 #include "surface.hpp"
37 #include "font.hpp"
38 #include "main.hpp"
39 #include "gameconfig.hpp"
40 #include "texture.hpp"
41 #include "texture_manager.hpp"
42 #include "obstack/obstackpp.hpp"
43
44 namespace SDL
45 {
46   Renderer::Renderer()
47   {
48     int flags = SDL_SWSURFACE;
49     if(config->use_fullscreen)
50       flags |= SDL_FULLSCREEN;
51     int width = config->screenwidth;
52     int height = config->screenheight;
53     int bpp = 0;
54
55     screen = SDL_SetVideoMode(width, height, bpp, flags);
56     if(screen == 0) {
57       std::stringstream msg;
58       msg << "Couldn't set video mode (" << width << "x" << height
59           << "-" << bpp << "bpp): " << SDL_GetError();
60       throw std::runtime_error(msg.str());
61     }
62
63     if(texture_manager == 0)
64       texture_manager = new TextureManager();
65   }
66
67   Renderer::~Renderer()
68   {
69   }
70
71   void
72   Renderer::draw_surface(const DrawingRequest& request)
73   {
74     //FIXME: support parameters request.alpha, request.angle, request.blend
75     const Surface* surface = (const Surface*) request.request_data;
76     SDL::Texture *sdltexture = dynamic_cast<Texture *>(surface->get_texture());
77     DrawingEffect effect = request.drawing_effect;
78     if (surface->get_flipx()) effect = HORIZONTAL_FLIP;
79
80     SDL_Surface *transform = sdltexture->get_transform(request.color, effect);
81
82     // get and check SDL_Surface
83     if (transform == 0) {
84       std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl;
85       return;
86     }   
87
88     int ox, oy;
89     if (effect == HORIZONTAL_FLIP)
90     {
91       ox = sdltexture->get_texture_width() - surface->get_x() - surface->get_width();
92     }
93     else
94     {
95       ox = surface->get_x();
96     }
97     if (effect == VERTICAL_FLIP)
98     {
99       oy = sdltexture->get_texture_height() - surface->get_y() - surface->get_height();
100     }
101     else
102     {
103       oy = surface->get_y();
104     }
105
106     int numerator, denominator;
107     float xfactor = (float) config->screenwidth / SCREEN_WIDTH;
108     float yfactor = (float) config->screenheight / SCREEN_HEIGHT;
109     if(xfactor < yfactor)
110     {
111       numerator = config->screenwidth;
112       denominator = SCREEN_WIDTH;
113     }
114     else
115     {
116       numerator = config->screenheight;
117       denominator = SCREEN_HEIGHT;
118     }
119
120     SDL_Rect srcRect;
121     srcRect.x = ox * numerator / denominator;
122     srcRect.y = oy * numerator / denominator;
123     srcRect.w = surface->get_width() * numerator / denominator;
124     srcRect.h = surface->get_height() * numerator / denominator;
125
126     SDL_Rect dstRect;
127     dstRect.x = (int) request.pos.x * numerator / denominator;
128     dstRect.y = (int) request.pos.y * numerator / denominator;
129
130     SDL_BlitSurface(transform, &srcRect, screen, &dstRect);
131   }
132
133   void
134   Renderer::draw_surface_part(const DrawingRequest& request)
135   {
136     const SurfacePartRequest* surfacepartrequest
137       = (SurfacePartRequest*) request.request_data;
138
139     const Surface* surface = surfacepartrequest->surface;
140     SDL::Texture *sdltexture = dynamic_cast<Texture *>(surface->get_texture());
141     DrawingEffect effect = request.drawing_effect;
142     if (surface->get_flipx()) effect = HORIZONTAL_FLIP;
143
144     SDL_Surface *transform = sdltexture->get_transform(Color(1.0, 1.0, 1.0), effect);
145
146     // get and check SDL_Surface
147     if (transform == 0) {
148       std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl;
149       return;
150     }   
151
152     int ox, oy;
153     if (effect == HORIZONTAL_FLIP)
154     {
155       ox = sdltexture->get_texture_width() - surface->get_x() - (int) surfacepartrequest->size.x;
156     }
157     else
158     {
159       ox = surface->get_x();
160     }
161     if (effect == VERTICAL_FLIP)
162     {
163       oy = sdltexture->get_texture_height() - surface->get_y() - (int) surfacepartrequest->size.y;
164     }
165     else
166     {
167       oy = surface->get_y();
168     }
169
170     int numerator, denominator;
171     float xfactor = (float) config->screenwidth / SCREEN_WIDTH;
172     float yfactor = (float) config->screenheight / SCREEN_HEIGHT;
173     if(xfactor < yfactor)
174     {
175       numerator = config->screenwidth;
176       denominator = SCREEN_WIDTH;
177     }
178     else
179     {
180       numerator = config->screenheight;
181       denominator = SCREEN_HEIGHT;
182     }
183
184     SDL_Rect srcRect;
185     srcRect.x = (ox + (int) surfacepartrequest->source.x) * numerator / denominator;
186     srcRect.y = (oy + (int) surfacepartrequest->source.y) * numerator / denominator;
187     srcRect.w = (int) surfacepartrequest->size.x * numerator / denominator;
188     srcRect.h = (int) surfacepartrequest->size.y * numerator / denominator;
189
190     SDL_Rect dstRect;
191     dstRect.x = (int) request.pos.x * numerator / denominator;
192     dstRect.y = (int) request.pos.y * numerator / denominator;
193
194     SDL_BlitSurface(transform, &srcRect, screen, &dstRect);
195   }
196
197   void
198   Renderer::draw_gradient(const DrawingRequest& request)
199   {
200     const GradientRequest* gradientrequest 
201       = (GradientRequest*) request.request_data;
202     const Color& top = gradientrequest->top;
203     const Color& bottom = gradientrequest->bottom;
204
205     for(int y = 0;y < screen->h;++y)
206     {
207       Uint8 r = (Uint8)((((float)(top.red-bottom.red)/(0-screen->h)) * y + top.red) * 255);
208       Uint8 g = (Uint8)((((float)(top.green-bottom.green)/(0-screen->h)) * y + top.green) * 255);
209       Uint8 b = (Uint8)((((float)(top.blue-bottom.blue)/(0-screen->h)) * y + top.blue) * 255);
210       Uint8 a = (Uint8)((((float)(top.alpha-bottom.alpha)/(0-screen->h)) * y + top.alpha) * 255);
211       Uint32 color = SDL_MapRGB(screen->format, r, g, b);
212
213       SDL_Rect rect;
214       rect.x = 0;
215       rect.y = y;
216       rect.w = screen->w;
217       rect.h = 1;
218
219       if(a == SDL_ALPHA_OPAQUE) {
220         SDL_FillRect(screen, &rect, color);
221       } else if(a != SDL_ALPHA_TRANSPARENT) {
222         SDL_Surface *temp = SDL_CreateRGBSurface(screen->flags, rect.w, rect.h, screen->format->BitsPerPixel, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask);
223
224         SDL_FillRect(temp, 0, color);
225         SDL_SetAlpha(temp, SDL_SRCALPHA, a);
226         SDL_BlitSurface(temp, 0, screen, &rect);
227         SDL_FreeSurface(temp);
228       }
229     }
230   }
231
232   void
233   Renderer::draw_text(const DrawingRequest& request)
234   {
235     const TextRequest* textrequest = (TextRequest*) request.request_data;
236
237     textrequest->font->draw(this, textrequest->text, request.pos,
238         textrequest->alignment, request.drawing_effect, request.alpha);
239   }
240
241   void
242   Renderer::draw_filled_rect(const DrawingRequest& request)
243   {
244     const FillRectRequest* fillrectrequest
245       = (FillRectRequest*) request.request_data;
246
247     SDL_Rect rect;
248     rect.x = (Sint16)request.pos.x * screen->w / SCREEN_WIDTH;
249     rect.y = (Sint16)request.pos.y * screen->h / SCREEN_HEIGHT;
250     rect.w = (Uint16)fillrectrequest->size.x * screen->w / SCREEN_WIDTH;
251     rect.h = (Uint16)fillrectrequest->size.y * screen->h / SCREEN_HEIGHT;
252     Uint8 r = static_cast<Uint8>(fillrectrequest->color.red * 255);
253     Uint8 g = static_cast<Uint8>(fillrectrequest->color.green * 255);
254     Uint8 b = static_cast<Uint8>(fillrectrequest->color.blue * 255);
255     Uint8 a = static_cast<Uint8>(fillrectrequest->color.alpha * 255);
256     Uint32 color = SDL_MapRGB(screen->format, r, g, b);
257     if(a == SDL_ALPHA_OPAQUE) {
258       SDL_FillRect(screen, &rect, color);
259     } else if(a != SDL_ALPHA_TRANSPARENT) {
260       SDL_Surface *temp = SDL_CreateRGBSurface(screen->flags, rect.w, rect.h, screen->format->BitsPerPixel, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask);
261
262       SDL_FillRect(temp, 0, color);
263       SDL_SetAlpha(temp, SDL_SRCALPHA, a);
264       SDL_BlitSurface(temp, 0, screen, &rect);
265       SDL_FreeSurface(temp);
266     }
267   }
268
269   void 
270   Renderer::do_take_screenshot()
271   {
272     // [Christoph] TODO: Yes, this method also takes care of the actual disk I/O. Split it?
273
274     SDL_Surface *shot_surf;
275     shot_surf = SDL_GetVideoSurface();
276     shot_surf->refcount++;
277
278     // save screenshot
279     static const std::string writeDir = PHYSFS_getWriteDir();
280     static const std::string dirSep = PHYSFS_getDirSeparator();
281     static const std::string baseName = "screenshot";
282     static const std::string fileExt = ".bmp";
283     std::string fullFilename;
284     for (int num = 0; num < 1000; num++) {
285       std::ostringstream oss;
286       oss << baseName;
287       oss << std::setw(3) << std::setfill('0') << num;
288       oss << fileExt;
289       std::string fileName = oss.str();
290       fullFilename = writeDir + dirSep + fileName;
291       if (!PHYSFS_exists(fileName.c_str())) {
292         SDL_SaveBMP(shot_surf, fullFilename.c_str());
293         log_debug << "Wrote screenshot to \"" << fullFilename << "\"" << std::endl;
294         SDL_FreeSurface(shot_surf);
295         return;
296       }
297     }
298     log_warning << "Did not save screenshot, because all files up to \"" << fullFilename << "\" already existed" << std::endl;
299     SDL_FreeSurface(shot_surf);
300   }
301
302   void
303   Renderer::flip()
304   {
305     SDL_Flip(screen);
306   }
307 }