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