The Battle for Wesnoth  1.15.1+dev
video.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
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 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "video.hpp"
16 
17 #include "display.hpp"
18 #include "floating_label.hpp"
19 #include "font/sdl_ttf.hpp"
20 #include "picture.hpp"
21 #include "log.hpp"
22 #include "preferences/general.hpp"
23 #include "sdl/point.hpp"
24 #include "sdl/userevent.hpp"
25 #include "sdl/utils.hpp"
26 #include "sdl/window.hpp"
27 
28 #include <cassert>
29 #include <vector>
30 
31 static lg::log_domain log_display("display");
32 #define LOG_DP LOG_STREAM(info, log_display)
33 #define ERR_DP LOG_STREAM(err, log_display)
34 
35 #define MAGIC_DPI_SCALE_NUMBER 96
36 
37 CVideo* CVideo::singleton_ = nullptr;
38 
39 namespace
40 {
41 surface frameBuffer = nullptr;
42 bool fake_interactive = false;
43 }
44 
45 namespace video2
46 {
47 std::list<events::sdl_handler*> draw_layers;
48 
49 draw_layering::draw_layering(const bool auto_join)
50  : sdl_handler(auto_join)
51 {
52  draw_layers.push_back(this);
53 }
54 
56 {
57  draw_layers.remove(this);
58 
60 }
61 
63 {
64  SDL_Event event;
65  event.type = SDL_WINDOWEVENT;
66  event.window.event = SDL_WINDOWEVENT_RESIZED;
67  event.window.data1 = (*frameBuffer).h;
68  event.window.data2 = (*frameBuffer).w;
69 
70  for(const auto& layer : draw_layers) {
71  layer->handle_window_event(event);
72  }
73 
74  SDL_Event drawEvent;
76 
77  drawEvent.type = DRAW_ALL_EVENT;
78  drawEvent.user = data;
79  SDL_FlushEvent(DRAW_ALL_EVENT);
80  SDL_PushEvent(&drawEvent);
81 }
82 
83 } // video2
84 
86  : window()
87  , fake_screen_(false)
88  , help_string_(0)
89  , updated_locked_(0)
90  , flip_locked_(0)
91  , refresh_rate_(0)
92 {
93  assert(!singleton_);
94  singleton_ = this;
95 
96  initSDL();
97 
98  switch(type) {
99  case NO_FAKE:
100  break;
101  case FAKE:
102  make_fake();
103  break;
104  case FAKE_TEST:
105  make_test_fake();
106  break;
107  }
108 }
109 
111 {
112  const int res = SDL_InitSubSystem(SDL_INIT_VIDEO);
113 
114  if(res < 0) {
115  ERR_DP << "Could not initialize SDL_video: " << SDL_GetError() << std::endl;
116  throw CVideo::error();
117  }
118 }
119 
121 {
122  if(sdl_get_version() >= version_info(2, 0, 6)) {
123  // Because SDL will free the framebuffer,
124  // ensure that we won't attempt to free it.
125  frameBuffer.clear_without_free();
126  }
127 
128  LOG_DP << "calling SDL_Quit()\n";
129  SDL_Quit();
130  assert(singleton_);
131  singleton_ = nullptr;
132  LOG_DP << "called SDL_Quit()\n";
133 }
134 
136 {
137  return fake_interactive ? false : (window == nullptr);
138 }
139 
141 {
142  if(event.type == SDL_WINDOWEVENT) {
143  switch(event.window.event) {
144  case SDL_WINDOWEVENT_RESIZED:
145  case SDL_WINDOWEVENT_RESTORED:
146  case SDL_WINDOWEVENT_SHOWN:
147  case SDL_WINDOWEVENT_EXPOSED:
148  // if(display::get_singleton())
149  // display::get_singleton()->redraw_everything();
150  SDL_Event drawEvent;
152 
153  drawEvent.type = DRAW_ALL_EVENT;
154  drawEvent.user = data;
155 
156  SDL_FlushEvent(DRAW_ALL_EVENT);
157  SDL_PushEvent(&drawEvent);
158  break;
159  }
160  }
161 }
162 
163 void CVideo::blit_surface(int x, int y, surface surf, SDL_Rect* srcrect, SDL_Rect* clip_rect)
164 {
165  surface& target(getSurface());
166  SDL_Rect dst{x, y, 0, 0};
167 
168  const clip_rect_setter clip_setter(target, clip_rect, clip_rect != nullptr);
169  sdl_blit(surf, srcrect, target, &dst);
170 }
171 
173 {
174  fake_screen_ = true;
175  refresh_rate_ = 1;
176 
177 #if SDL_VERSION_ATLEAST(2, 0, 6)
178  frameBuffer = SDL_CreateRGBSurfaceWithFormat(0, 16, 16, 24, SDL_PIXELFORMAT_BGR888);
179 #else
180  frameBuffer = SDL_CreateRGBSurface(0, 16, 16, 24, 0xFF0000, 0xFF00, 0xFF, 0);
181 #endif
182 }
183 
184 void CVideo::make_test_fake(const unsigned width, const unsigned height)
185 {
186 #if SDL_VERSION_ATLEAST(2, 0, 6)
187  frameBuffer = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_BGR888);
188 #else
189  frameBuffer = SDL_CreateRGBSurface(0, width, height, 32, 0xFF0000, 0xFF00, 0xFF, 0);
190 #endif
191 
192  fake_interactive = true;
193  refresh_rate_ = 1;
194 }
195 
197 {
198  if(!window) {
199  return;
200  }
201 
202  surface fb = SDL_GetWindowSurface(*window);
203 
204  if(frameBuffer && sdl_get_version() >= version_info(2, 0, 6)) {
205  // Because SDL has already freed the old framebuffer,
206  // ensure that we won't attempt to free it.
207  frameBuffer.clear_without_free();
208  }
209 
210  frameBuffer = fb;
211 }
212 
214 {
215  // Position
216  const int x = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
217  const int y = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
218 
219  // Dimensions
220  const point res = preferences::resolution();
221  const int w = res.x;
222  const int h = res.y;
223 
224  uint32_t window_flags = 0;
225 
226  // Add any more default flags here
227  window_flags |= SDL_WINDOW_RESIZABLE;
228 #ifdef __APPLE__
229  window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
230 #endif
231 
233  window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
234  } else if(preferences::maximized()) {
235  window_flags |= SDL_WINDOW_MAXIMIZED;
236  }
237 
238  // Initialize window
239  window.reset(new sdl::window("", x, y, w, h, window_flags, SDL_RENDERER_SOFTWARE));
240 
241  std::cerr << "Setting mode to " << w << "x" << h << std::endl;
242 
244 
245  SDL_DisplayMode currentDisplayMode;
246  SDL_GetCurrentDisplayMode(window->get_display_index(), &currentDisplayMode);
247  refresh_rate_ = currentDisplayMode.refresh_rate != 0 ? currentDisplayMode.refresh_rate : 60;
248 
250 
252 }
253 
255 {
256  assert(window);
257  if(fake_screen_) {
258  return;
259  }
260 
261  switch(mode) {
262  case TO_FULLSCREEN:
263  window->full_screen();
264  break;
265 
266  case TO_WINDOWED:
267  window->to_window();
268  window->restore();
269  break;
270 
271  case TO_MAXIMIZED_WINDOW:
272  window->to_window();
273  window->maximize();
274  break;
275 
276  case TO_RES:
277  window->restore();
278  window->set_size(size.x, size.y);
279  window->center();
280  break;
281  }
282 
284 }
285 
286 SDL_Rect CVideo::screen_area(bool as_pixels) const
287 {
288  if(!window) {
289  return {0, 0, frameBuffer->w, frameBuffer->h};
290  }
291 
292  // First, get the renderer size in pixels.
293  SDL_Point size = window->get_output_size();
294 
295  // Then convert the dimensions into screen coordinates, if applicable.
296  if(!as_pixels) {
297  float scale_x, scale_y;
298  std::tie(scale_x, scale_y) = get_dpi_scale_factor();
299 
300  size.x /= scale_x;
301  size.y /= scale_y;
302  }
303 
304  return {0, 0, size.x, size.y};
305 }
306 
307 int CVideo::get_width(bool as_pixels) const
308 {
309  return screen_area(as_pixels).w;
310 }
311 
312 int CVideo::get_height(bool as_pixels) const
313 {
314  return screen_area(as_pixels).h;
315 }
316 
317 void CVideo::delay(unsigned int milliseconds)
318 {
319  if(!game_config::no_delay) {
320  SDL_Delay(milliseconds);
321  }
322 }
323 
325 {
326  if(fake_screen_ || flip_locked_ > 0) {
327  return;
328  }
329 
330  if(window) {
331  window->render();
332  }
333 }
334 
335 void CVideo::lock_updates(bool value)
336 {
337  if(value == true) {
338  ++updated_locked_;
339  } else {
340  --updated_locked_;
341  }
342 }
343 
345 {
346  return updated_locked_ > 0;
347 }
348 
349 void CVideo::set_window_title(const std::string& title)
350 {
351  assert(window);
352  window->set_title(title);
353 }
354 
356 {
357  assert(window);
358  window->set_icon(icon);
359 }
360 
362 {
363  if(!window) {
364  return;
365  }
366 
367  window->fill(0, 0, 0, 255);
368 }
369 
371 {
372  return window.get();
373 }
374 
375 bool CVideo::window_has_flags(uint32_t flags) const
376 {
377  if(!window) {
378  return false;
379  }
380 
381  return (window->get_flags() & flags) != 0;
382 }
383 
384 std::pair<float, float> CVideo::get_dpi_scale_factor() const
385 {
386  std::pair<float, float> result{1.0f, 1.0f};
387 
388  if(!window) {
389  return result;
390  }
391 
392  float hdpi, vdpi;
393  SDL_GetDisplayDPI(window->get_display_index(), nullptr, &hdpi, &vdpi);
394 
395  result.first = hdpi / MAGIC_DPI_SCALE_NUMBER;
396  result.second = vdpi / MAGIC_DPI_SCALE_NUMBER;
397 
398  return result;
399 }
400 
401 std::vector<point> CVideo::get_available_resolutions(const bool include_current)
402 {
403  std::vector<point> result;
404 
405  if(!window) {
406  return result;
407  }
408 
409  const int display_index = window->get_display_index();
410 
411  const int modes = SDL_GetNumDisplayModes(display_index);
412  if(modes <= 0) {
413  std::cerr << "No modes supported\n";
414  return result;
415  }
416 
418 
419 #if 0
420  // DPI scale factor.
421  float scale_h, scale_v;
422  std::tie(scale_h, scale_v) = get_dpi_scale_factor();
423 #endif
424 
425  // The maximum size to which this window can be set. For some reason this won't
426  // pop up as a display mode of its own.
427  SDL_Rect bounds;
428  SDL_GetDisplayBounds(display_index, &bounds);
429 
430  SDL_DisplayMode mode;
431 
432  for(int i = 0; i < modes; ++i) {
433  if(SDL_GetDisplayMode(display_index, i, &mode) == 0) {
434  // Exclude any results outside the range of the current DPI.
435  if(mode.w > bounds.w && mode.h > bounds.h) {
436  continue;
437  }
438 
439  if(mode.w >= min_res.x && mode.h >= min_res.y) {
440  result.emplace_back(mode.w, mode.h);
441  }
442  }
443  }
444 
445  if(std::find(result.begin(), result.end(), min_res) == result.end()) {
446  result.push_back(min_res);
447  }
448 
449  if(include_current) {
450  result.push_back(current_resolution());
451  }
452 
453  std::sort(result.begin(), result.end());
454  result.erase(std::unique(result.begin(), result.end()), result.end());
455 
456  return result;
457 }
458 
460 {
461  return frameBuffer;
462 }
463 
465 {
466  return point(window->get_size()); // Convert from plain SDL_Point
467 }
468 
470 {
471  return (window->get_flags() & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
472 }
473 
474 int CVideo::set_help_string(const std::string& str)
475 {
477 
478  const color_t color{0, 0, 0, 0xbb};
479 
480  int size = font::SIZE_LARGE;
481 
482  while(size > 0) {
483  if(font::line_width(str, size) > get_width()) {
484  size--;
485  } else {
486  break;
487  }
488  }
489 
490  const int border = 5;
491 
492  font::floating_label flabel(str);
493  flabel.set_font_size(size);
494  flabel.set_position(get_width() / 2, get_height());
495  flabel.set_bg_color(color);
496  flabel.set_border_size(border);
497 
499 
500  const SDL_Rect& rect = font::get_floating_label_rect(help_string_);
501  font::move_floating_label(help_string_, 0.0, -double(rect.h));
502 
503  return help_string_;
504 }
505 
507 {
508  if(handle == help_string_) {
510  help_string_ = 0;
511  }
512 }
513 
515 {
517 }
518 
519 void CVideo::set_fullscreen(bool ison)
520 {
521  if(window && is_fullscreen() != ison) {
522  const point& res = preferences::resolution();
523 
524  MODE_EVENT mode;
525 
526  if(ison) {
527  mode = TO_FULLSCREEN;
528  } else {
530  }
531 
532  set_window_mode(mode, res);
533 
534  if(display* d = display::get_singleton()) {
535  d->redraw_everything();
536  }
537  }
538 
539  // Change the config value.
541 }
542 
544 {
546 }
547 
548 bool CVideo::set_resolution(const unsigned width, const unsigned height)
549 {
550  return set_resolution(point(width, height));
551 }
552 
554 {
555  if(resolution == current_resolution()) {
556  return false;
557  }
558 
559  set_window_mode(TO_RES, resolution);
560 
561  if(display* d = display::get_singleton()) {
562  d->redraw_everything();
563  }
564 
565  // Change the saved values in preferences.
566  preferences::_set_resolution(resolution);
568 
569  // Push a window-resized event to the queue. This is necessary so various areas
570  // of the game (like GUI2) update properly with the new size.
572 
573  return true;
574 }
575 
576 void CVideo::lock_flips(bool lock)
577 {
578  if(lock) {
579  ++flip_locked_;
580  } else {
581  --flip_locked_;
582  }
583 }
void raise_resize_event()
Definition: events.cpp:764
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:355
void _set_fullscreen(bool ison)
Definition: general.cpp:410
draw_layering(const bool auto_join=true)
Definition: video.cpp:49
bool update_locked() const
Whether the screen has been &#39;locked&#39; or not.
Definition: video.cpp:344
point current_resolution()
Definition: video.cpp:464
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:88
const int min_window_height
Definition: general.cpp:66
std::list< events::sdl_handler * > draw_layers
Definition: video.cpp:47
void _set_maximized(bool ison)
Definition: general.cpp:405
FAKE_TYPES
Definition: video.hpp:37
std::pair< float, float > get_dpi_scale_factor() const
The current scale factor on High-DPI screens.
Definition: video.cpp:384
#define ERR_DP
Definition: video.cpp:33
#define LOG_DP
Definition: video.cpp:32
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
const int min_window_width
Definition: general.cpp:65
int help_string_
Curent ID of the help string.
Definition: video.hpp:271
Definition: video.hpp:31
void flip()
Renders the screen.
Definition: video.cpp:324
void lock_updates(bool value)
Stop the screen being redrawn.
Definition: video.cpp:335
void remove_floating_label(int handle)
removes the floating label given by &#39;handle&#39; from the screen
void _set_resolution(const point &res)
Definition: general.cpp:399
static CVideo * singleton_
Definition: video.hpp:241
MODE_EVENT
Definition: video.hpp:76
int refresh_rate_
Definition: video.hpp:275
bool non_interactive() const
Definition: video.cpp:135
int flip_locked_
Definition: video.hpp:274
#define h
void set_font_size(int font_size)
void blit_surface(int x, int y, surface surf, SDL_Rect *srcrect=nullptr, SDL_Rect *clip_rect=nullptr)
Draws a surface directly onto the screen framebuffer.
Definition: video.cpp:163
#define d
int updated_locked_
Definition: video.hpp:273
-file util.hpp
~CVideo()
Definition: video.cpp:120
surface & getSurface()
Returns a reference to the framebuffer.
Definition: video.cpp:459
bool maximized()
Definition: general.cpp:389
void move_floating_label(int handle, double xmove, double ymove)
moves the floating label given by &#39;handle&#39; by (xmove,ymove)
int x
x coordinate.
Definition: point.hpp:44
virtual ~draw_layering()
Definition: video.cpp:55
bool set_resolution(const unsigned width, const unsigned height)
Definition: video.cpp:548
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:349
void make_fake()
Definition: video.cpp:172
void initSDL()
Initializes the SDL video subsystem.
Definition: video.cpp:110
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
The wrapper class for the SDL_Window class.
Definition: window.hpp:44
void make_test_fake(const unsigned width=1024, const unsigned height=768)
Creates a fake frame buffer for the unit tests.
Definition: video.cpp:184
void lock_flips(bool)
Definition: video.cpp:576
bool fullscreen()
Definition: general.cpp:394
static lg::log_domain log_display("display")
void set_bg_color(const color_t &bg_color)
void set_position(double xpos, double ypos)
map_display and display: classes which take care of displaying the map and game-data on the screen...
sdl::window * get_window()
Returns a pointer to the underlying SDL window.
Definition: video.cpp:370
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:307
#define DRAW_ALL_EVENT
Definition: events.hpp:29
Definition: video.cpp:45
virtual void handle_window_event(const SDL_Event &event)
Definition: video.cpp:140
std::size_t i
Definition: function.cpp:933
void init_window()
Initializes a new SDL window instance, taking into account any preiously saved states.
Definition: video.cpp:213
void toggle_fullscreen()
Definition: video.cpp:543
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
void set_fullscreen(bool ison)
Definition: video.cpp:519
Holds a 2D point.
Definition: point.hpp:23
SDL_Rect get_floating_label_rect(int handle)
int w
int get_height(bool as_pixels=true) const
Returns the window renderer height in pixels or in screen coordinates.
Definition: video.cpp:312
static int sort(lua_State *L)
Definition: ltablib.cpp:411
version_info sdl_get_version()
Definition: utils.cpp:33
void set_border_size(int border)
Represents version numbers.
int set_help_string(const std::string &str)
Displays a help string with the given text.
Definition: video.cpp:474
const int SIZE_LARGE
Definition: constants.cpp:27
#define MAGIC_DPI_SCALE_NUMBER
Definition: video.cpp:35
bool is_fullscreen() const
Definition: video.cpp:469
bool find(E event, F functor)
Tests whether an event handler is available.
void trigger_full_redraw()
Definition: video.cpp:62
Standard logging facilities (interface).
video_event_handler event_handler_
Definition: video.hpp:268
void clear_all_help_strings()
Removes all help strings.
Definition: video.cpp:514
static void delay(unsigned int milliseconds)
Waits a given number of milliseconds before returning.
Definition: video.cpp:317
void set_window_mode(const MODE_EVENT mode, const point &size)
Sets the window&#39;s mode - ie, changing it to fullscreen, maximizing, etc.
Definition: video.cpp:254
Contains a wrapper class for the SDL_Window class.
bool fake_screen_
Definition: video.hpp:250
point resolution()
Definition: general.cpp:373
void update_framebuffer()
Updates and ensures the framebuffer surface is valid.
Definition: video.cpp:196
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:33
CVideo(const CVideo &)=delete
SDL_Rect screen_area(bool as_pixels=true) const
Returns the current window renderer area, either in pixels or screen coordinates. ...
Definition: video.cpp:286
void clear_screen()
Clear the screen contents.
Definition: video.cpp:361
std::shared_ptr< halo_record > handle
Definition: halo.hpp:31
int y
y coordinate.
Definition: point.hpp:47
std::vector< point > get_available_resolutions(const bool include_current=false)
Returns the list of available screen resolutions.
Definition: video.cpp:401
bool window_has_flags(uint32_t flags) const
Tests whether the given flags are currently set on the SDL window.
Definition: video.cpp:375
virtual void join_global()
Definition: events.cpp:365
int line_width(const std::string &line, int font_size, int style)
Determine the width of a line of text given a certain font size.
Definition: sdl_ttf.cpp:416
std::unique_ptr< sdl::window > window
The SDL window object.
Definition: video.hpp:244
void clear_help_string(int handle)
Removes the help string with the given handle.
Definition: video.cpp:506