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  uint32_t renderer_flags = SDL_RENDERER_SOFTWARE;
225  LOG_DP << "VSYNC on\n";
226  renderer_flags |= SDL_RENDERER_PRESENTVSYNC;
227  }
228 
229  // Initialize window
230  window.reset(new sdl::window("", x, y, w, h, window_flags, renderer_flags));
231 
232  std::cerr << "Setting mode to " << w << "x" << h << std::endl;
233 
235 
236  SDL_DisplayMode currentDisplayMode;
237  SDL_GetCurrentDisplayMode(window->get_display_index(), &currentDisplayMode);
238  refresh_rate_ = currentDisplayMode.refresh_rate != 0 ? currentDisplayMode.refresh_rate : 60;
239 
241 
243 }
244 
246 {
247  assert(window);
248  if(fake_screen_) {
249  return;
250  }
251 
252  switch(mode) {
253  case TO_FULLSCREEN:
254  window->full_screen();
255  break;
256 
257  case TO_WINDOWED:
258  window->to_window();
259  window->restore();
260  break;
261 
262  case TO_MAXIMIZED_WINDOW:
263  window->to_window();
264  window->maximize();
265  break;
266 
267  case TO_RES:
268  window->restore();
269  window->set_size(size.x, size.y);
270  window->center();
271  break;
272  }
273 
275 }
276 
277 SDL_Rect CVideo::screen_area(bool as_pixels) const
278 {
279  if(!window) {
280  return {0, 0, frameBuffer->w, frameBuffer->h};
281  }
282 
283  // First, get the renderer size in pixels.
284  SDL_Point size = window->get_output_size();
285 
286  // Then convert the dimensions into screen coordinates, if applicable.
287  if(!as_pixels) {
288  auto [scale_x, scale_y] = get_dpi_scale_factor();
289 
290  size.x /= scale_x;
291  size.y /= scale_y;
292  }
293 
294  return {0, 0, size.x, size.y};
295 }
296 
297 int CVideo::get_width(bool as_pixels) const
298 {
299  return screen_area(as_pixels).w;
300 }
301 
302 int CVideo::get_height(bool as_pixels) const
303 {
304  return screen_area(as_pixels).h;
305 }
306 
307 void CVideo::delay(unsigned int milliseconds)
308 {
309  if(!game_config::no_delay) {
310  SDL_Delay(milliseconds);
311  }
312 }
313 
315 {
316  if(fake_screen_ || flip_locked_ > 0) {
317  return;
318  }
319 
320  if(window) {
321  window->render();
322  }
323 }
324 
325 void CVideo::lock_updates(bool value)
326 {
327  if(value == true) {
328  ++updated_locked_;
329  } else {
330  --updated_locked_;
331  }
332 }
333 
335 {
336  return updated_locked_ > 0;
337 }
338 
339 void CVideo::set_window_title(const std::string& title)
340 {
341  assert(window);
342  window->set_title(title);
343 }
344 
346 {
347  assert(window);
348  window->set_icon(icon);
349 }
350 
352 {
353  if(!window) {
354  return;
355  }
356 
357  window->fill(0, 0, 0, 255);
358 }
359 
361 {
362  return window.get();
363 }
364 
366 {
367  const char* const drvname = SDL_GetCurrentVideoDriver();
368  return drvname ? drvname : "<not initialized>";
369 }
370 
371 std::vector<std::string> CVideo::enumerate_drivers()
372 {
373  std::vector<std::string> res;
374  int num_drivers = SDL_GetNumVideoDrivers();
375 
376  for(int n = 0; n < num_drivers; ++n) {
377  const char* drvname = SDL_GetVideoDriver(n);
378  res.emplace_back(drvname ? drvname : "<invalid driver>");
379  }
380 
381  return res;
382 }
383 
384 bool CVideo::window_has_flags(uint32_t flags) const
385 {
386  if(!window) {
387  return false;
388  }
389 
390  return (window->get_flags() & flags) != 0;
391 }
392 
393 std::pair<float, float> CVideo::get_dpi() const
394 {
395  float hdpi, vdpi;
396  if(window && SDL_GetDisplayDPI(window->get_display_index(), nullptr, &hdpi, &vdpi) == 0) {
397 #ifdef TARGET_OS_OSX
398  // SDL 2.0.12 changes SDL_GetDisplayDPI. Function now returns DPI
399  // multiplied by screen's scale factor. This part of code reverts
400  // this multiplication.
401  //
402  // For more info see issue: https://github.com/wesnoth/wesnoth/issues/5019
403 
404  if(sdl_get_version() >= version_info{2, 0, 12}) {
405  float scale_factor = desktop::apple::get_scale_factor(window->get_display_index());
406  hdpi /= scale_factor;
407  vdpi /= scale_factor;
408  }
409 #endif
410  return { hdpi, vdpi };
411  }
412  // SDL doesn't know the screen dpi, there's a configuration issue, or we
413  // don't have a window yet.
414  return { 0.0f, 0.0f };
415 }
416 
417 std::pair<float, float> CVideo::get_dpi_scale_factor() const
418 {
419  auto dpi = get_dpi();
420  if(dpi.first != 0.0f && dpi.second != 0.0f) {
421  return { dpi.first / MAGIC_DPI_SCALE_NUMBER, dpi.second / MAGIC_DPI_SCALE_NUMBER };
422  }
423  // Assume a scale factor of 1.0 if the screen dpi is currently unknown.
424  return { 1.0f, 1.0f };
425 }
426 
427 std::vector<point> CVideo::get_available_resolutions(const bool include_current)
428 {
429  std::vector<point> result;
430 
431  if(!window) {
432  return result;
433  }
434 
435  const int display_index = window->get_display_index();
436 
437  const int modes = SDL_GetNumDisplayModes(display_index);
438  if(modes <= 0) {
439  std::cerr << "No modes supported\n";
440  return result;
441  }
442 
444 
445 #if 0
446  // DPI scale factor.
447  auto [scale_h, scale_v] = get_dpi_scale_factor();
448 #endif
449 
450  // The maximum size to which this window can be set. For some reason this won't
451  // pop up as a display mode of its own.
452  SDL_Rect bounds;
453  SDL_GetDisplayBounds(display_index, &bounds);
454 
455  SDL_DisplayMode mode;
456 
457  for(int i = 0; i < modes; ++i) {
458  if(SDL_GetDisplayMode(display_index, i, &mode) == 0) {
459  // Exclude any results outside the range of the current DPI.
460  if(mode.w > bounds.w && mode.h > bounds.h) {
461  continue;
462  }
463 
464  if(mode.w >= min_res.x && mode.h >= min_res.y) {
465  result.emplace_back(mode.w, mode.h);
466  }
467  }
468  }
469 
470  if(std::find(result.begin(), result.end(), min_res) == result.end()) {
471  result.push_back(min_res);
472  }
473 
474  if(include_current) {
475  result.push_back(current_resolution());
476  }
477 
478  std::sort(result.begin(), result.end());
479  result.erase(std::unique(result.begin(), result.end()), result.end());
480 
481  return result;
482 }
483 
485 {
486  return frameBuffer;
487 }
488 
490 {
491  return point(window->get_size()); // Convert from plain SDL_Point
492 }
493 
495 {
496  return (window->get_flags() & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
497 }
498 
500 {
501  return sdl_get_version() >= version_info{2, 0, 17};
502 }
503 
504 int CVideo::set_help_string(const std::string& str)
505 {
507 
508  const color_t color{0, 0, 0, 0xbb};
509 
510  int size = font::SIZE_LARGE;
511 
512  while(size > 0) {
513  if(font::pango_line_width(str, size) > get_width()) {
514  size--;
515  } else {
516  break;
517  }
518  }
519 
520  const int border = 5;
521 
522  font::floating_label flabel(str);
523  flabel.set_font_size(size);
524  flabel.set_position(get_width() / 2, get_height());
525  flabel.set_bg_color(color);
526  flabel.set_border_size(border);
527 
529 
530  const SDL_Rect& rect = font::get_floating_label_rect(help_string_);
531  font::move_floating_label(help_string_, 0.0, -double(rect.h));
532 
533  return help_string_;
534 }
535 
537 {
538  if(handle == help_string_) {
540  help_string_ = 0;
541  }
542 }
543 
545 {
547 }
548 
549 void CVideo::set_fullscreen(bool ison)
550 {
551  if(window && is_fullscreen() != ison) {
552  const point& res = preferences::resolution();
553 
554  MODE_EVENT mode;
555 
556  if(ison) {
557  mode = TO_FULLSCREEN;
558  } else {
560  }
561 
562  set_window_mode(mode, res);
563 
564  if(display* d = display::get_singleton()) {
565  d->redraw_everything();
566  }
567  }
568 
569  // Change the config value.
571 }
572 
574 {
576 }
577 
578 bool CVideo::set_resolution(const unsigned width, const unsigned height)
579 {
580  return set_resolution(point(width, height));
581 }
582 
584 {
585  if(resolution == current_resolution()) {
586  return false;
587  }
588 
589  set_window_mode(TO_RES, resolution);
590 
591  if(display* d = display::get_singleton()) {
592  d->redraw_everything();
593  }
594 
595  // Change the saved values in preferences.
596  preferences::_set_resolution(resolution);
598 
599  // Push a window-resized event to the queue. This is necessary so various areas
600  // of the game (like GUI2) update properly with the new size.
602 
603  return true;
604 }
605 
606 void CVideo::lock_flips(bool lock)
607 {
608  if(lock) {
609  ++flip_locked_;
610  } else {
611  --flip_locked_;
612  }
613 }
void raise_resize_event()
Definition: events.cpp:762
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:345
void _set_fullscreen(bool ison)
Definition: general.cpp:435
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:334
point current_resolution()
Definition: video.cpp:489
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:430
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:417
#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:291
Definition: video.hpp:32
void flip()
Renders the screen.
Definition: video.cpp:314
void lock_updates(bool value)
Stop the screen being redrawn.
Definition: video.cpp:325
bool vsync()
Definition: general.cpp:419
void _set_resolution(const point &res)
Definition: general.cpp:424
static CVideo * singleton_
Definition: video.hpp:261
MODE_EVENT
Definition: video.hpp:91
int refresh_rate_
Definition: video.hpp:295
bool non_interactive() const
Definition: video.cpp:135
int flip_locked_
Definition: video.hpp:294
#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:293
~CVideo()
Definition: video.cpp:126
surface & getSurface()
Returns a reference to the framebuffer.
Definition: video.cpp:484
bool supports_vsync() const
Definition: video.cpp:499
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:578
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:339
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:606
static std::vector< std::string > enumerate_drivers()
Definition: video.cpp:371
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:360
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:297
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:573
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:549
static std::string current_driver()
Definition: video.cpp:365
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:302
static int sort(lua_State *L)
Definition: ltablib.cpp:397
version_info sdl_get_version()
Definition: utils.cpp:34
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:504
const int SIZE_LARGE
Definition: constants.cpp:30
bool is_fullscreen() const
Definition: video.cpp:494
void trigger_full_redraw()
Definition: video.cpp:68
Standard logging facilities (interface).
video_event_handler event_handler_
Definition: video.hpp:288
void clear_all_help_strings()
Removes all help strings.
Definition: video.cpp:544
static void delay(unsigned int milliseconds)
Waits a given number of milliseconds before returning.
Definition: video.cpp:307
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:245
Contains a wrapper class for the SDL_Window class.
bool fake_screen_
Definition: video.hpp:270
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:277
void clear_screen()
Clear the screen contents.
Definition: video.cpp:351
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:427
bool window_has_flags(uint32_t flags) const
Tests whether the given flags are currently set on the SDL window.
Definition: video.cpp:384
virtual void join_global()
Definition: events.cpp:363
std::unique_ptr< sdl::window > window
The SDL window object.
Definition: video.hpp:264
std::pair< float, float > get_dpi() const
The current game screen dpi.
Definition: video.cpp:393
void clear_help_string(int handle)
Removes the help string with the given handle.
Definition: video.cpp:536
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.