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