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