Cleaned up coordinate translation a little, SDL mouse handling is still broken and...
[supertux.git] / src / video / sdl / sdl_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/sdl/sdl_renderer.hpp"
19
20 #include "util/log.hpp"
21 #include "video/drawing_request.hpp"
22 #include "video/sdl/sdl_surface_data.hpp"
23 #include "video/sdl/sdl_texture.hpp"
24 #include "video/sdl/sdl_painter.hpp"
25
26 #include <iomanip>
27 #include <iostream>
28 #include <physfs.h>
29 #include <sstream>
30 #include <stdexcept>
31 #include "SDL2/SDL_video.h"
32
33 SDLRenderer::SDLRenderer() :
34   window(),
35   renderer(),
36   desktop_size()
37 {
38   Renderer::instance_ = this;
39
40   SDL_DisplayMode mode;
41   SDL_GetCurrentDisplayMode(0, &mode);
42   desktop_size = Size(mode.w, mode.h);
43
44   log_info << "creating SDLRenderer" << std::endl;
45   int width  = g_config->window_size.width;
46   int height = g_config->window_size.height;
47
48   int flags = SDL_WINDOW_RESIZABLE;
49   if(g_config->use_fullscreen)
50   {
51     flags |= SDL_WINDOW_FULLSCREEN;
52     width  = g_config->fullscreen_size.width;
53     height = g_config->fullscreen_size.height;
54   }
55
56   SCREEN_WIDTH = width;
57   SCREEN_HEIGHT = height;
58
59   PHYSICAL_SCREEN_WIDTH = width;
60   PHYSICAL_SCREEN_HEIGHT = height;
61
62   int ret = SDL_CreateWindowAndRenderer(width, height, flags,
63                                         &window, &renderer);
64
65   if(ret != 0) {
66     std::stringstream msg;
67     msg << "Couldn't set video mode (" << width << "x" << height
68         << "): " << SDL_GetError();
69     throw std::runtime_error(msg.str());
70   }
71
72   SDL_RendererInfo info;
73   if (SDL_GetRendererInfo(renderer, &info) != 0)
74   {
75     log_warning << "Couldn't get RendererInfo: " << SDL_GetError() << std::endl;
76   }
77   else
78   {
79     log_info << "SDL_Renderer: " << info.name << std::endl;
80     log_info << "SDL_RendererFlags: " << std::endl;
81     if (info.flags & SDL_RENDERER_SOFTWARE) log_info << "  SDL_RENDERER_SOFTWARE" << std::endl;
82     if (info.flags & SDL_RENDERER_ACCELERATED) log_info << "  SDL_RENDERER_ACCELERATED" << std::endl;
83     if (info.flags & SDL_RENDERER_PRESENTVSYNC) log_info << "  SDL_RENDERER_PRESENTVSYNC" << std::endl;
84     if (info.flags & SDL_RENDERER_TARGETTEXTURE) log_info << "  SDL_RENDERER_TARGETTEXTURE" << std::endl;
85     log_info << "Texture Formats: " << std::endl;
86     for(size_t i = 0; i < info.num_texture_formats; ++i)
87     {
88       log_info << "  " << SDL_GetPixelFormatName(info.texture_formats[i]) << std::endl;
89     }
90     log_info << "Max Texture Width: " << info.max_texture_width << std::endl;
91     log_info << "Max Texture Height: " << info.max_texture_height << std::endl;
92   }
93
94   if(texture_manager == 0)
95     texture_manager = new TextureManager();
96
97   apply_config();
98 }
99
100 SDLRenderer::~SDLRenderer()
101 {
102   SDL_DestroyRenderer(renderer);
103   SDL_DestroyWindow(window);
104 }
105
106 void
107 SDLRenderer::draw_surface(const DrawingRequest& request)
108 {
109   SDLPainter::draw_surface(renderer, request);
110 }
111
112 void
113 SDLRenderer::draw_surface_part(const DrawingRequest& request)
114 {
115   SDLPainter::draw_surface_part(renderer, request);
116 }
117
118 void
119 SDLRenderer::draw_gradient(const DrawingRequest& request)
120 {
121   SDLPainter::draw_gradient(renderer, request);
122 }
123
124 void
125 SDLRenderer::draw_filled_rect(const DrawingRequest& request)
126 {
127   SDLPainter::draw_filled_rect(renderer, request);
128 }
129
130 void
131 SDLRenderer::draw_inverse_ellipse(const DrawingRequest& request)
132 {
133   SDLPainter::draw_inverse_ellipse(renderer, request);
134 }
135
136 void 
137 SDLRenderer::do_take_screenshot()
138 {
139   // [Christoph] TODO: Yes, this method also takes care of the actual disk I/O. Split it?
140   int width;
141   int height;
142   if (SDL_GetRendererOutputSize(renderer, &width, &height) != 0)
143   {
144     log_warning << "SDL_GetRenderOutputSize failed: " << SDL_GetError() << std::endl;
145   }
146   else
147   {
148 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
149     Uint32 rmask = 0xff000000;
150     Uint32 gmask = 0x00ff0000;
151     Uint32 bmask = 0x0000ff00;
152     Uint32 amask = 0x000000ff;
153 #else
154     Uint32 rmask = 0x000000ff;
155     Uint32 gmask = 0x0000ff00;
156     Uint32 bmask = 0x00ff0000;
157     Uint32 amask = 0xff000000;
158 #endif
159     SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32,
160                                                 rmask, gmask, bmask, amask);
161     if (!surface)
162     {
163       log_warning << "SDL_CreateRGBSurface failed: " << SDL_GetError() << std::endl;
164     }
165     else
166     {
167       int ret = SDL_RenderReadPixels(renderer, NULL,
168                                      SDL_PIXELFORMAT_ABGR8888,
169                                      surface->pixels,
170                                      surface->pitch);
171       if (ret != 0)
172       {
173         log_warning << "SDL_RenderReadPixels failed: " << SDL_GetError() << std::endl;
174       }
175       else
176       {
177         // save screenshot
178         static const std::string writeDir = PHYSFS_getWriteDir();
179         static const std::string dirSep = PHYSFS_getDirSeparator();
180         static const std::string baseName = "screenshot";
181         static const std::string fileExt = ".bmp";
182         std::string fullFilename;
183         for (int num = 0; num < 1000; num++) {
184           std::ostringstream oss;
185           oss << baseName;
186           oss << std::setw(3) << std::setfill('0') << num;
187           oss << fileExt;
188           std::string fileName = oss.str();
189           fullFilename = writeDir + dirSep + fileName;
190           if (!PHYSFS_exists(fileName.c_str())) {
191             SDL_SaveBMP(surface, fullFilename.c_str());
192             log_debug << "Wrote screenshot to \"" << fullFilename << "\"" << std::endl;
193             return;
194           }
195         }
196         log_warning << "Did not save screenshot, because all files up to \"" << fullFilename << "\" already existed" << std::endl;
197       }
198     }
199   }
200 }
201
202 void
203 SDLRenderer::flip()
204 {
205   SDL_RenderPresent(renderer);
206 }
207
208 void
209 SDLRenderer::resize(int w , int h)
210 {
211   g_config->window_size = Size(w, h);
212
213   PHYSICAL_SCREEN_WIDTH = w;
214   PHYSICAL_SCREEN_HEIGHT = h;
215
216   apply_config();
217 }
218
219 void
220 SDLRenderer::apply_config()
221 {
222   if (false)
223   {
224     log_info << "Applying Config:" 
225              << "\n  Desktop: " << desktop_size.width << "x" << desktop_size.height
226              << "\n  Window:  " << g_config->window_size
227              << "\n  FullRes: " << g_config->fullscreen_size
228              << "\n  Aspect:  " << g_config->aspect_size
229              << "\n  Magnif:  " << g_config->magnification
230              << std::endl;
231   }
232
233   float target_aspect = static_cast<float>(desktop_size.width) / static_cast<float>(desktop_size.height);
234   if (g_config->aspect_size != Size(0, 0))
235   {
236     target_aspect = float(g_config->aspect_size.width) / float(g_config->aspect_size.height);
237   }
238
239   float desktop_aspect = 4.0f / 3.0f; // random default fallback guess
240   if (desktop_size.width != -1 && desktop_size.height != -1)
241   {
242     desktop_aspect = float(desktop_size.width) / float(desktop_size.height);
243   }
244
245   Size screen_size;
246
247   // Get the screen width
248   if (g_config->use_fullscreen)
249   {
250     screen_size = g_config->fullscreen_size;
251     desktop_aspect = float(screen_size.width) / float(screen_size.height);
252   }
253   else
254   {
255     screen_size = g_config->window_size;
256   }
257
258   //apply_video_mode(screen_size, g_config->use_fullscreen);
259
260   if (target_aspect > 1.0f)
261   {
262     SCREEN_WIDTH  = static_cast<int>(screen_size.width * (target_aspect / desktop_aspect));
263     SCREEN_HEIGHT = static_cast<int>(screen_size.height);
264   }
265   else
266   {
267     SCREEN_WIDTH  = static_cast<int>(screen_size.width);
268     SCREEN_HEIGHT = static_cast<int>(screen_size.height  * (target_aspect / desktop_aspect));
269   }
270
271   Size max_size(1280, 800);
272   Size min_size(640, 480);
273
274   if (g_config->magnification == 0.0f) // Magic value that means 'minfill'
275   {
276     // This scales SCREEN_WIDTH/SCREEN_HEIGHT so that they never excede
277     // max_size.width/max_size.height resp. min_size.width/min_size.height
278     if (SCREEN_WIDTH > max_size.width || SCREEN_HEIGHT > max_size.height)
279     {
280       float scale1  = float(max_size.width)/SCREEN_WIDTH;
281       float scale2  = float(max_size.height)/SCREEN_HEIGHT;
282       float scale   = (scale1 < scale2) ? scale1 : scale2;
283       SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  * scale);
284       SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT * scale);
285     } 
286     else if (SCREEN_WIDTH < min_size.width || SCREEN_HEIGHT < min_size.height)
287     {
288       float scale1  = float(min_size.width)/SCREEN_WIDTH;
289       float scale2  = float(min_size.height)/SCREEN_HEIGHT;
290       float scale   = (scale1 < scale2) ? scale1 : scale2;
291       SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  * scale);
292       SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT * scale);
293     }
294   }
295   else
296   {
297     SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  / g_config->magnification);
298     SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT / g_config->magnification);
299
300     // This works by adding black borders around the screen to limit
301     // SCREEN_WIDTH/SCREEN_HEIGHT to max_size.width/max_size.height
302     Size new_size = screen_size;
303
304     if (SCREEN_WIDTH > max_size.width)
305     {
306       new_size.width = static_cast<int>((float) new_size.width * float(max_size.width)/SCREEN_WIDTH);
307       SCREEN_WIDTH = static_cast<int>(max_size.width);
308     }
309
310     if (SCREEN_HEIGHT > max_size.height)
311     {
312       new_size.height = static_cast<int>((float) new_size.height * float(max_size.height)/SCREEN_HEIGHT);
313       SCREEN_HEIGHT = static_cast<int>(max_size.height);
314     }
315   }
316   SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT);
317 }
318
319 Vector
320 SDLRenderer::to_logical(int physical_x, int physical_y, bool foobar)
321 {
322   if (foobar)
323   {
324     // SDL translates coordinates automatically, except for SDL_GetMouseState(), thus foobar
325     return Vector(physical_x * float(SCREEN_WIDTH) / (PHYSICAL_SCREEN_WIDTH),
326                   physical_y * float(SCREEN_HEIGHT) / (PHYSICAL_SCREEN_HEIGHT));
327   }
328   else
329   {
330     // SDL is doing the translation internally, so we have nothing to do
331     return Vector(physical_x, physical_y);
332   }
333 }
334
335 void
336 SDLRenderer::set_gamma(float gamma)
337 {
338   Uint16 ramp[256];
339   SDL_CalculateGammaRamp(gamma, ramp);
340   SDL_SetWindowGammaRamp(window, ramp, ramp, ramp);
341 }
342
343 /* EOF */