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