The Battle for Wesnoth  1.15.10+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 #ifdef TARGET_OS_OSX
29 #include "desktop/apple_video.hpp"
30 #include "game_version.hpp"
31 #endif
32 
33 #include <cassert>
34 #include <vector>
35 
36 static lg::log_domain log_display("display");
37 #define LOG_DP LOG_STREAM(info, log_display)
38 #define ERR_DP LOG_STREAM(err, log_display)
39 
40 CVideo* CVideo::singleton_ = nullptr;
41 
42 namespace
43 {
44 surface frameBuffer = nullptr;
45 bool fake_interactive = false;
46 
47 const unsigned MAGIC_DPI_SCALE_NUMBER = 96;
48 }
49 
50 namespace video2
51 {
52 std::list<events::sdl_handler*> draw_layers;
53 
54 draw_layering::draw_layering(const bool auto_join)
55  : sdl_handler(auto_join)
56 {
57  draw_layers.push_back(this);
58 }
59 
61 {
62  draw_layers.remove(this);
63 
65 }
66 
68 {
69  SDL_Event event;
70  event.type = SDL_WINDOWEVENT;
71  event.window.event = SDL_WINDOWEVENT_RESIZED;
72  event.window.data1 = (*frameBuffer).h;
73  event.window.data2 = (*frameBuffer).w;
74 
75  for(const auto& layer : draw_layers) {
76  layer->handle_window_event(event);
77  }
78 
79  SDL_Event drawEvent;
81 
82  drawEvent.type = DRAW_ALL_EVENT;
83  drawEvent.user = data;
84  SDL_FlushEvent(DRAW_ALL_EVENT);
85  SDL_PushEvent(&drawEvent);
86 }
87 
88 } // video2
89 
91  : window()
92  , fake_screen_(false)
93  , help_string_(0)
94  , updated_locked_(0)
95  , flip_locked_(0)
96  , refresh_rate_(0)
97 {
98  assert(!singleton_);
99  singleton_ = this;
100 
101  initSDL();
102 
103  switch(type) {
104  case NO_FAKE:
105  break;
106  case FAKE:
107  make_fake();
108  break;
109  case FAKE_TEST:
110  make_test_fake();
111  break;
112  }
113 }
114 
116 {
117  const int res = SDL_InitSubSystem(SDL_INIT_VIDEO);
118 
119  if(res < 0) {
120  ERR_DP << "Could not initialize SDL_video: " << SDL_GetError() << std::endl;
121  throw CVideo::error();
122  }
123 }
124 
126 {
127  if(sdl_get_version() >= version_info(2, 0, 6)) {
128  // Because SDL will free the framebuffer,
129  // ensure that we won't attempt to free it.
130  frameBuffer.clear_without_free();
131  }
132 
133  LOG_DP << "calling SDL_Quit()\n";
134  SDL_Quit();
135  assert(singleton_);
136  singleton_ = nullptr;
137  LOG_DP << "called SDL_Quit()\n";
138 }
139 
141 {
142  return fake_interactive ? false : (window == nullptr);
143 }
144 
146 {
147  if(event.type == SDL_WINDOWEVENT) {
148  switch(event.window.event) {
149  case SDL_WINDOWEVENT_RESIZED:
150  case SDL_WINDOWEVENT_RESTORED:
151  case SDL_WINDOWEVENT_SHOWN:
152  case SDL_WINDOWEVENT_EXPOSED:
153  // if(display::get_singleton())
154  // display::get_singleton()->redraw_everything();
155  SDL_Event drawEvent;
157 
158  drawEvent.type = DRAW_ALL_EVENT;
159  drawEvent.user = data;
160 
161  SDL_FlushEvent(DRAW_ALL_EVENT);
162  SDL_PushEvent(&drawEvent);
163  break;
164  }
165  }
166 }
167 
168 void CVideo::blit_surface(int x, int y, surface surf, SDL_Rect* srcrect, SDL_Rect* clip_rect)
169 {
170  surface& target(getSurface());
171  SDL_Rect dst{x, y, 0, 0};
172 
173  const clip_rect_setter clip_setter(target, clip_rect, clip_rect != nullptr);
174  sdl_blit(surf, srcrect, target, &dst);
175 }
176 
178 {
179  fake_screen_ = true;
180  refresh_rate_ = 1;
181 
182 #if SDL_VERSION_ATLEAST(2, 0, 6)
183  frameBuffer = SDL_CreateRGBSurfaceWithFormat(0, 16, 16, 24, SDL_PIXELFORMAT_BGR888);
184 #else
185  frameBuffer = SDL_CreateRGBSurface(0, 16, 16, 24, 0xFF0000, 0xFF00, 0xFF, 0);
186 #endif
187 }
188 
189 void CVideo::make_test_fake(const unsigned width, const unsigned height)
190 {
191 #if SDL_VERSION_ATLEAST(2, 0, 6)
192  frameBuffer = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_BGR888);
193 #else
194  frameBuffer = SDL_CreateRGBSurface(0, width, height, 32, 0xFF0000, 0xFF00, 0xFF, 0);
195 #endif
196 
197  fake_interactive = true;
198  refresh_rate_ = 1;
199 }
200 
202 {
203  if(!window) {
204  return;
205  }
206 
207  surface fb = SDL_GetWindowSurface(*window);
208 
209  if(frameBuffer && sdl_get_version() >= version_info(2, 0, 6)) {
210  // Because SDL has already freed the old framebuffer,
211  // ensure that we won't attempt to free it.
212  frameBuffer.clear_without_free();
213  }
214 
215  frameBuffer = fb;
216 }
217 
219 {
220  // Position
221  const int x = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
222  const int y = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
223 
224  // Dimensions
225  const point res = preferences::resolution();
226  const int w = res.x;
227  const int h = res.y;
228 
229  uint32_t window_flags = 0;
230 
231  // Add any more default flags here
232  window_flags |= SDL_WINDOW_RESIZABLE;
233 #ifdef __APPLE__
234  window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
235 #endif
236 
238  window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
239  } else if(preferences::maximized()) {
240  window_flags |= SDL_WINDOW_MAXIMIZED;
241  }
242 
243  // Initialize window
244  window.reset(new sdl::window("", x, y, w, h, window_flags, SDL_RENDERER_SOFTWARE));
245 
246  std::cerr << "Setting mode to " << w << "x" << h << std::endl;
247 
249 
250  SDL_DisplayMode currentDisplayMode;
251  SDL_GetCurrentDisplayMode(window->get_display_index(), &currentDisplayMode);
252  refresh_rate_ = currentDisplayMode.refresh_rate != 0 ? currentDisplayMode.refresh_rate : 60;
253 
255 
257 }
258 
260 {
261  assert(window);
262  if(fake_screen_) {
263  return;
264  }
265 
266  switch(mode) {
267  case TO_FULLSCREEN:
268  window->full_screen();
269  break;
270 
271  case TO_WINDOWED:
272  window->to_window();
273  window->restore();
274  break;
275 
276  case TO_MAXIMIZED_WINDOW:
277  window->to_window();
278  window->maximize();
279  break;
280 
281  case TO_RES:
282  window->restore();
283  window->set_size(size.x, size.y);
284  window->center();
285  break;
286  }
287 
289 }
290 
291 SDL_Rect CVideo::screen_area(bool as_pixels) const
292 {
293  if(!window) {
294  return {0, 0, frameBuffer->w, frameBuffer->h};
295  }
296 
297  // First, get the renderer size in pixels.
298  SDL_Point size = window->get_output_size();
299 
300  // Then convert the dimensions into screen coordinates, if applicable.
301  if(!as_pixels) {
302  auto [scale_x, scale_y] = get_dpi_scale_factor();
303 
304  size.x /= scale_x;
305  size.y /= scale_y;
306  }
307 
308  return {0, 0, size.x, size.y};
309 }
310 
311 int CVideo::get_width(bool as_pixels) const
312 {
313  return screen_area(as_pixels).w;
314 }
315 
316 int CVideo::get_height(bool as_pixels) const
317 {
318  return screen_area(as_pixels).h;
319 }
320 
321 void CVideo::delay(unsigned int milliseconds)
322 {
323  if(!game_config::no_delay) {
324  SDL_Delay(milliseconds);
325  }
326 }
327 
329 {
330  if(fake_screen_ || flip_locked_ > 0) {
331  return;
332  }
333 
334  if(window) {
335  window->render();
336  }
337 }
338 
339 void CVideo::lock_updates(bool value)
340 {
341  if(value == true) {
342  ++updated_locked_;
343  } else {
344  --updated_locked_;
345  }
346 }
347 
349 {
350  return updated_locked_ > 0;
351 }
352 
353 void CVideo::set_window_title(const std::string& title)
354 {
355  assert(window);
356  window->set_title(title);
357 }
358 
360 {
361  assert(window);
362  window->set_icon(icon);
363 }
364 
366 {
367  if(!window) {
368  return;
369  }
370 
371  window->fill(0, 0, 0, 255);
372 }
373 
375 {
376  return window.get();
377 }
378 
380 {
381  const char* const drvname = SDL_GetCurrentVideoDriver();
382  return drvname ? drvname : "<not initialized>";
383 }
384 
385 std::vector<std::string> CVideo::enumerate_drivers()
386 {
387  std::vector<std::string> res;
388  int num_drivers = SDL_GetNumVideoDrivers();
389 
390  for(int n = 0; n < num_drivers; ++n) {
391  const char* drvname = SDL_GetVideoDriver(n);
392  res.emplace_back(drvname ? drvname : "<invalid driver>");
393  }
394 
395  return res;
396 }
397 
398 bool CVideo::window_has_flags(uint32_t flags) const
399 {
400  if(!window) {
401  return false;
402  }
403 
404  return (window->get_flags() & flags) != 0;
405 }
406 
407 std::pair<float, float> CVideo::get_dpi() const
408 {
409  float hdpi, vdpi;
410  if(window && SDL_GetDisplayDPI(window->get_display_index(), nullptr, &hdpi, &vdpi) == 0) {
411 #ifdef TARGET_OS_OSX
412  // SDL 2.0.12 changes SDL_GetDisplayDPI. Function now returns DPI
413  // multiplied by screen's scale factor. This part of code reverts
414  // this multiplication.
415  //
416  // For more info see issue: https://github.com/wesnoth/wesnoth/issues/5019
417  SDL_version sdl_version;
418  SDL_GetVersion(&sdl_version);
419 
420  const version_info sdl_version_info(sdl_version.major, sdl_version.minor, sdl_version.patch);
421  const version_info version_to_compare(2, 0, 12);
422 
423  if (sdl_version_info >= version_to_compare) {
424  float scale_factor = desktop::apple::get_scale_factor(window->get_display_index());
425  hdpi /= scale_factor;
426  vdpi /= scale_factor;
427  }
428 #endif
429  return { hdpi, vdpi };
430  }
431  // SDL doesn't know the screen dpi, there's a configuration issue, or we
432  // don't have a window yet.
433  return { 0.0f, 0.0f };
434 }
435 
436 std::pair<float, float> CVideo::get_dpi_scale_factor() const
437 {
438  auto dpi = get_dpi();
439  if(dpi.first != 0.0f && dpi.second != 0.0f) {
440  return { dpi.first / MAGIC_DPI_SCALE_NUMBER, dpi.second / MAGIC_DPI_SCALE_NUMBER };
441  }
442  // Assume a scale factor of 1.0 if the screen dpi is currently unknown.
443  return { 1.0f, 1.0f };
444 }
445 
446 std::vector<point> CVideo::get_available_resolutions(const bool include_current)
447 {
448  std::vector<point> result;
449 
450  if(!window) {
451  return result;
452  }
453 
454  const int display_index = window->get_display_index();
455 
456  const int modes = SDL_GetNumDisplayModes(display_index);
457  if(modes <= 0) {
458  std::cerr << "No modes supported\n";
459  return result;
460  }
461 
463 
464 #if 0
465  // DPI scale factor.
466  auto [scale_h, scale_v] = get_dpi_scale_factor();
467 #endif
468 
469  // The maximum size to which this window can be set. For some reason this won't
470  // pop up as a display mode of its own.
471  SDL_Rect bounds;
472  SDL_GetDisplayBounds(display_index, &bounds);
473 
474  SDL_DisplayMode mode;
475 
476  for(int i = 0; i < modes; ++i) {
477  if(SDL_GetDisplayMode(display_index, i, &mode) == 0) {
478  // Exclude any results outside the range of the current DPI.
479  if(mode.w > bounds.w && mode.h > bounds.h) {
480  continue;
481  }
482 
483  if(mode.w >= min_res.x && mode.h >= min_res.y) {
484  result.emplace_back(mode.w, mode.h);
485  }
486  }
487  }
488 
489  if(std::find(result.begin(), result.end(), min_res) == result.end()) {
490  result.push_back(min_res);
491  }
492 
493  if(include_current) {
494  result.push_back(current_resolution());
495  }
496 
497  std::sort(result.begin(), result.end());
498  result.erase(std::unique(result.begin(), result.end()), result.end());
499 
500  return result;
501 }
502 
504 {
505  return frameBuffer;
506 }
507 
509 {
510  return point(window->get_size()); // Convert from plain SDL_Point
511 }
512 
514 {
515  return (window->get_flags() & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
516 }
517 
518 int CVideo::set_help_string(const std::string& str)
519 {
521 
522  const color_t color{0, 0, 0, 0xbb};
523 
524  int size = font::SIZE_LARGE;
525 
526  while(size > 0) {
527  if(font::line_width(str, size) > get_width()) {
528  size--;
529  } else {
530  break;
531  }
532  }
533 
534  const int border = 5;
535 
536  font::floating_label flabel(str);
537  flabel.set_font_size(size);
538  flabel.set_position(get_width() / 2, get_height());
539  flabel.set_bg_color(color);
540  flabel.set_border_size(border);
541 
543 
544  const SDL_Rect& rect = font::get_floating_label_rect(help_string_);
545  font::move_floating_label(help_string_, 0.0, -double(rect.h));
546 
547  return help_string_;
548 }
549 
551 {
552  if(handle == help_string_) {
554  help_string_ = 0;
555  }
556 }
557 
559 {
561 }
562 
563 void CVideo::set_fullscreen(bool ison)
564 {
565  if(window && is_fullscreen() != ison) {
566  const point& res = preferences::resolution();
567 
568  MODE_EVENT mode;
569 
570  if(ison) {
571  mode = TO_FULLSCREEN;
572  } else {
574  }
575 
576  set_window_mode(mode, res);
577 
578  if(display* d = display::get_singleton()) {
579  d->redraw_everything();
580  }
581  }
582 
583  // Change the config value.
585 }
586 
588 {
590 }
591 
592 bool CVideo::set_resolution(const unsigned width, const unsigned height)
593 {
594  return set_resolution(point(width, height));
595 }
596 
598 {
599  if(resolution == current_resolution()) {
600  return false;
601  }
602 
603  set_window_mode(TO_RES, resolution);
604 
605  if(display* d = display::get_singleton()) {
606  d->redraw_everything();
607  }
608 
609  // Change the saved values in preferences.
610  preferences::_set_resolution(resolution);
612 
613  // Push a window-resized event to the queue. This is necessary so various areas
614  // of the game (like GUI2) update properly with the new size.
616 
617  return true;
618 }
619 
620 void CVideo::lock_flips(bool lock)
621 {
622  if(lock) {
623  ++flip_locked_;
624  } else {
625  --flip_locked_;
626  }
627 }
void raise_resize_event()
Definition: events.cpp:761
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:359
void _set_fullscreen(bool ison)
Definition: general.cpp:424
draw_layering(const bool auto_join=true)
Definition: video.cpp:54
bool update_locked() const
Whether the screen has been &#39;locked&#39; or not.
Definition: video.cpp:348
point current_resolution()
Definition: video.cpp:508
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:90
const int min_window_height
Definition: general.cpp:66
std::list< events::sdl_handler * > draw_layers
Definition: video.cpp:52
void _set_maximized(bool ison)
Definition: general.cpp:419
Interfaces for manipulating version numbers of engine, add-ons, etc.
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:436
#define ERR_DP
Definition: video.cpp:38
#define LOG_DP
Definition: video.cpp:37
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:40
const int min_window_width
Definition: general.cpp:65
int help_string_
Curent ID of the help string.
Definition: video.hpp:288
Definition: video.hpp:31
void flip()
Renders the screen.
Definition: video.cpp:328
void lock_updates(bool value)
Stop the screen being redrawn.
Definition: video.cpp:339
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:413
static CVideo * singleton_
Definition: video.hpp:258
MODE_EVENT
Definition: video.hpp:90
int refresh_rate_
Definition: video.hpp:292
bool non_interactive() const
Definition: video.cpp:140
int flip_locked_
Definition: video.hpp:291
#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:168
#define d
int updated_locked_
Definition: video.hpp:290
~CVideo()
Definition: video.cpp:125
surface & getSurface()
Returns a reference to the framebuffer.
Definition: video.cpp:503
bool maximized()
Definition: general.cpp:403
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:60
bool set_resolution(const unsigned width, const unsigned height)
Definition: video.cpp:592
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:353
void make_fake()
Definition: video.cpp:177
void initSDL()
Initializes the SDL video subsystem.
Definition: video.cpp:115
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:189
void lock_flips(bool)
Definition: video.cpp:620
static std::vector< std::string > enumerate_drivers()
Definition: video.cpp:385
bool fullscreen()
Definition: general.cpp:408
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:374
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:311
CGFloat get_scale_factor(int display_index)
#define DRAW_ALL_EVENT
Definition: events.hpp:29
Definition: video.cpp:50
virtual void handle_window_event(const SDL_Event &event)
Definition: video.cpp:145
std::size_t i
Definition: function.cpp:934
void init_window()
Initializes a new SDL window instance, taking into account any preiously saved states.
Definition: video.cpp:218
void toggle_fullscreen()
Definition: video.cpp:587
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:563
static std::string current_driver()
Definition: video.cpp:379
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:316
static int sort(lua_State *L)
Definition: ltablib.cpp:397
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:518
const int SIZE_LARGE
Definition: constants.cpp:27
bool is_fullscreen() const
Definition: video.cpp:513
void trigger_full_redraw()
Definition: video.cpp:67
Standard logging facilities (interface).
video_event_handler event_handler_
Definition: video.hpp:285
void clear_all_help_strings()
Removes all help strings.
Definition: video.cpp:558
static void delay(unsigned int milliseconds)
Waits a given number of milliseconds before returning.
Definition: video.cpp:321
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:259
Contains a wrapper class for the SDL_Window class.
bool fake_screen_
Definition: video.hpp:267
point resolution()
Definition: general.cpp:387
void update_framebuffer()
Updates and ensures the framebuffer surface is valid.
Definition: video.cpp:201
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:31
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:291
void clear_screen()
Clear the screen contents.
Definition: video.cpp:365
static map_location::DIRECTION n
std::shared_ptr< halo_record > handle
Definition: halo.hpp:29
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:446
bool window_has_flags(uint32_t flags) const
Tests whether the given flags are currently set on the SDL window.
Definition: video.cpp:398
virtual void join_global()
Definition: events.cpp:362
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:378
std::unique_ptr< sdl::window > window
The SDL window object.
Definition: video.hpp:261
std::pair< float, float > get_dpi() const
The current game screen dpi.
Definition: video.cpp:407
void clear_help_string(int handle)
Removes the help string with the given handle.
Definition: video.cpp:550