The Battle for Wesnoth  1.17.0-dev
video.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
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_compat.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  LOG_DP << "calling SDL_Quit()\n";
128  SDL_Quit();
129  assert(singleton_);
130  singleton_ = nullptr;
131  LOG_DP << "called SDL_Quit()\n";
132 }
133 
135 {
136  return fake_interactive ? false : (window == nullptr);
137 }
138 
140 {
141  if(event.type == SDL_WINDOWEVENT) {
142  switch(event.window.event) {
143  case SDL_WINDOWEVENT_RESIZED:
144  case SDL_WINDOWEVENT_RESTORED:
145  case SDL_WINDOWEVENT_SHOWN:
146  case SDL_WINDOWEVENT_EXPOSED:
147  // if(display::get_singleton())
148  // display::get_singleton()->redraw_everything();
149  SDL_Event drawEvent;
151 
152  drawEvent.type = DRAW_ALL_EVENT;
153  drawEvent.user = data;
154 
155  SDL_FlushEvent(DRAW_ALL_EVENT);
156  SDL_PushEvent(&drawEvent);
157  break;
158  }
159  }
160 }
161 
162 void CVideo::blit_surface(int x, int y, surface surf, SDL_Rect* srcrect, SDL_Rect* clip_rect)
163 {
164  surface& target(getSurface());
165  SDL_Rect dst{x, y, 0, 0};
166 
167  const clip_rect_setter clip_setter(target, clip_rect, clip_rect != nullptr);
168  sdl_blit(surf, srcrect, target, &dst);
169 }
170 
172 {
173  fake_screen_ = true;
174  refresh_rate_ = 1;
175 
176  frameBuffer = SDL_CreateRGBSurfaceWithFormat(0, 16, 16, 24, SDL_PIXELFORMAT_BGR888);
177 }
178 
179 void CVideo::make_test_fake(const unsigned width, const unsigned height)
180 {
181  frameBuffer = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_BGR888);
182 
183  fake_interactive = true;
184  refresh_rate_ = 1;
185 }
186 
188 {
189  if(!window) {
190  return;
191  }
192 
193  surface fb = SDL_GetWindowSurface(*window);
194  frameBuffer = fb;
195 }
196 
198 {
199  // Position
200  const int x = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
201  const int y = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
202 
203  // Dimensions
204  const point res = preferences::resolution();
205  const int w = res.x;
206  const int h = res.y;
207 
208  uint32_t window_flags = 0;
209 
210  // Add any more default flags here
211  window_flags |= SDL_WINDOW_RESIZABLE;
212 #ifdef __APPLE__
213  window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
214 #endif
215 
217  window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
218  } else if(preferences::maximized()) {
219  window_flags |= SDL_WINDOW_MAXIMIZED;
220  }
221 
222  // Initialize window
223  window.reset(new sdl::window("", x, y, w, h, window_flags, SDL_RENDERER_SOFTWARE));
224 
225  std::cerr << "Setting mode to " << w << "x" << h << std::endl;
226 
228 
229  SDL_DisplayMode currentDisplayMode;
230  SDL_GetCurrentDisplayMode(window->get_display_index(), &currentDisplayMode);
231  refresh_rate_ = currentDisplayMode.refresh_rate != 0 ? currentDisplayMode.refresh_rate : 60;
232 
234 
236 }
237 
239 {
240  assert(window);
241  if(fake_screen_) {
242  return;
243  }
244 
245  switch(mode) {
246  case TO_FULLSCREEN:
247  window->full_screen();
248  break;
249 
250  case TO_WINDOWED:
251  window->to_window();
252  window->restore();
253  break;
254 
255  case TO_MAXIMIZED_WINDOW:
256  window->to_window();
257  window->maximize();
258  break;
259 
260  case TO_RES:
261  window->restore();
262  window->set_size(size.x, size.y);
263  window->center();
264  break;
265  }
266 
268 }
269 
270 SDL_Rect CVideo::screen_area(bool as_pixels) const
271 {
272  if(!window) {
273  return {0, 0, frameBuffer->w, frameBuffer->h};
274  }
275 
276  // First, get the renderer size in pixels.
277  SDL_Point size = window->get_output_size();
278 
279  // Then convert the dimensions into screen coordinates, if applicable.
280  if(!as_pixels) {
281  auto [scale_x, scale_y] = get_dpi_scale_factor();
282 
283  size.x /= scale_x;
284  size.y /= scale_y;
285  }
286 
287  return {0, 0, size.x, size.y};
288 }
289 
290 int CVideo::get_width(bool as_pixels) const
291 {
292  return screen_area(as_pixels).w;
293 }
294 
295 int CVideo::get_height(bool as_pixels) const
296 {
297  return screen_area(as_pixels).h;
298 }
299 
300 void CVideo::delay(unsigned int milliseconds)
301 {
302  if(!game_config::no_delay) {
303  SDL_Delay(milliseconds);
304  }
305 }
306 
308 {
309  if(fake_screen_ || flip_locked_ > 0) {
310  return;
311  }
312 
313  if(window) {
314  window->render();
315  }
316 }
317 
318 void CVideo::lock_updates(bool value)
319 {
320  if(value == true) {
321  ++updated_locked_;
322  } else {
323  --updated_locked_;
324  }
325 }
326 
328 {
329  return updated_locked_ > 0;
330 }
331 
332 void CVideo::set_window_title(const std::string& title)
333 {
334  assert(window);
335  window->set_title(title);
336 }
337 
339 {
340  assert(window);
341  window->set_icon(icon);
342 }
343 
345 {
346  if(!window) {
347  return;
348  }
349 
350  window->fill(0, 0, 0, 255);
351 }
352 
354 {
355  return window.get();
356 }
357 
359 {
360  const char* const drvname = SDL_GetCurrentVideoDriver();
361  return drvname ? drvname : "<not initialized>";
362 }
363 
364 std::vector<std::string> CVideo::enumerate_drivers()
365 {
366  std::vector<std::string> res;
367  int num_drivers = SDL_GetNumVideoDrivers();
368 
369  for(int n = 0; n < num_drivers; ++n) {
370  const char* drvname = SDL_GetVideoDriver(n);
371  res.emplace_back(drvname ? drvname : "<invalid driver>");
372  }
373 
374  return res;
375 }
376 
377 bool CVideo::window_has_flags(uint32_t flags) const
378 {
379  if(!window) {
380  return false;
381  }
382 
383  return (window->get_flags() & flags) != 0;
384 }
385 
386 std::pair<float, float> CVideo::get_dpi() const
387 {
388  float hdpi, vdpi;
389  if(window && SDL_GetDisplayDPI(window->get_display_index(), nullptr, &hdpi, &vdpi) == 0) {
390 #ifdef TARGET_OS_OSX
391  // SDL 2.0.12 changes SDL_GetDisplayDPI. Function now returns DPI
392  // multiplied by screen's scale factor. This part of code reverts
393  // this multiplication.
394  //
395  // For more info see issue: https://github.com/wesnoth/wesnoth/issues/5019
396  SDL_version sdl_version;
397  SDL_GetVersion(&sdl_version);
398 
399  const version_info sdl_version_info(sdl_version.major, sdl_version.minor, sdl_version.patch);
400  const version_info version_to_compare(2, 0, 12);
401 
402  if (sdl_version_info >= version_to_compare) {
403  float scale_factor = desktop::apple::get_scale_factor(window->get_display_index());
404  hdpi /= scale_factor;
405  vdpi /= scale_factor;
406  }
407 #endif
408  return { hdpi, vdpi };
409  }
410  // SDL doesn't know the screen dpi, there's a configuration issue, or we
411  // don't have a window yet.
412  return { 0.0f, 0.0f };
413 }
414 
415 std::pair<float, float> CVideo::get_dpi_scale_factor() const
416 {
417  auto dpi = get_dpi();
418  if(dpi.first != 0.0f && dpi.second != 0.0f) {
419  return { dpi.first / MAGIC_DPI_SCALE_NUMBER, dpi.second / MAGIC_DPI_SCALE_NUMBER };
420  }
421  // Assume a scale factor of 1.0 if the screen dpi is currently unknown.
422  return { 1.0f, 1.0f };
423 }
424 
425 std::vector<point> CVideo::get_available_resolutions(const bool include_current)
426 {
427  std::vector<point> result;
428 
429  if(!window) {
430  return result;
431  }
432 
433  const int display_index = window->get_display_index();
434 
435  const int modes = SDL_GetNumDisplayModes(display_index);
436  if(modes <= 0) {
437  std::cerr << "No modes supported\n";
438  return result;
439  }
440 
442 
443 #if 0
444  // DPI scale factor.
445  auto [scale_h, scale_v] = get_dpi_scale_factor();
446 #endif
447 
448  // The maximum size to which this window can be set. For some reason this won't
449  // pop up as a display mode of its own.
450  SDL_Rect bounds;
451  SDL_GetDisplayBounds(display_index, &bounds);
452 
453  SDL_DisplayMode mode;
454 
455  for(int i = 0; i < modes; ++i) {
456  if(SDL_GetDisplayMode(display_index, i, &mode) == 0) {
457  // Exclude any results outside the range of the current DPI.
458  if(mode.w > bounds.w && mode.h > bounds.h) {
459  continue;
460  }
461 
462  if(mode.w >= min_res.x && mode.h >= min_res.y) {
463  result.emplace_back(mode.w, mode.h);
464  }
465  }
466  }
467 
468  if(std::find(result.begin(), result.end(), min_res) == result.end()) {
469  result.push_back(min_res);
470  }
471 
472  if(include_current) {
473  result.push_back(current_resolution());
474  }
475 
476  std::sort(result.begin(), result.end());
477  result.erase(std::unique(result.begin(), result.end()), result.end());
478 
479  return result;
480 }
481 
483 {
484  return frameBuffer;
485 }
486 
488 {
489  return point(window->get_size()); // Convert from plain SDL_Point
490 }
491 
493 {
494  return (window->get_flags() & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
495 }
496 
497 int CVideo::set_help_string(const std::string& str)
498 {
500 
501  const color_t color{0, 0, 0, 0xbb};
502 
503  int size = font::SIZE_LARGE;
504 
505  while(size > 0) {
506  if(font::pango_line_width(str, size) > get_width()) {
507  size--;
508  } else {
509  break;
510  }
511  }
512 
513  const int border = 5;
514 
515  font::floating_label flabel(str);
516  flabel.set_font_size(size);
517  flabel.set_position(get_width() / 2, get_height());
518  flabel.set_bg_color(color);
519  flabel.set_border_size(border);
520 
522 
523  const SDL_Rect& rect = font::get_floating_label_rect(help_string_);
524  font::move_floating_label(help_string_, 0.0, -double(rect.h));
525 
526  return help_string_;
527 }
528 
530 {
531  if(handle == help_string_) {
533  help_string_ = 0;
534  }
535 }
536 
538 {
540 }
541 
542 void CVideo::set_fullscreen(bool ison)
543 {
544  if(window && is_fullscreen() != ison) {
545  const point& res = preferences::resolution();
546 
547  MODE_EVENT mode;
548 
549  if(ison) {
550  mode = TO_FULLSCREEN;
551  } else {
553  }
554 
555  set_window_mode(mode, res);
556 
557  if(display* d = display::get_singleton()) {
558  d->redraw_everything();
559  }
560  }
561 
562  // Change the config value.
564 }
565 
567 {
569 }
570 
571 bool CVideo::set_resolution(const unsigned width, const unsigned height)
572 {
573  return set_resolution(point(width, height));
574 }
575 
577 {
578  if(resolution == current_resolution()) {
579  return false;
580  }
581 
582  set_window_mode(TO_RES, resolution);
583 
584  if(display* d = display::get_singleton()) {
585  d->redraw_everything();
586  }
587 
588  // Change the saved values in preferences.
589  preferences::_set_resolution(resolution);
591 
592  // Push a window-resized event to the queue. This is necessary so various areas
593  // of the game (like GUI2) update properly with the new size.
595 
596  return true;
597 }
598 
599 void CVideo::lock_flips(bool lock)
600 {
601  if(lock) {
602  ++flip_locked_;
603  } else {
604  --flip_locked_;
605  }
606 }
void raise_resize_event()
Definition: events.cpp:761
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:338
void _set_fullscreen(bool ison)
Definition: general.cpp:429
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:327
point current_resolution()
Definition: video.cpp:487
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:91
const int min_window_height
Definition: general.cpp:66
std::list< events::sdl_handler * > draw_layers
Definition: video.cpp:52
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:424
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:415
#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:307
void lock_updates(bool value)
Stop the screen being redrawn.
Definition: video.cpp:318
void _set_resolution(const point &res)
Definition: general.cpp:418
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:134
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:162
#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:482
bool maximized()
Definition: general.cpp:408
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:571
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:332
void make_fake()
Definition: video.cpp:171
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:84
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:179
void lock_flips(bool)
Definition: video.cpp:599
static std::vector< std::string > enumerate_drivers()
Definition: video.cpp:364
bool fullscreen()
Definition: general.cpp:413
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:353
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:290
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:139
std::size_t i
Definition: function.cpp:940
void init_window()
Initializes a new SDL window instance, taking into account any preiously saved states.
Definition: video.cpp:197
void toggle_fullscreen()
Definition: video.cpp:566
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:542
static std::string current_driver()
Definition: video.cpp:358
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:295
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:497
const int SIZE_LARGE
Definition: constants.cpp:29
bool is_fullscreen() const
Definition: video.cpp:492
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:537
static void delay(unsigned int milliseconds)
Waits a given number of milliseconds before returning.
Definition: video.cpp:300
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:238
Contains a wrapper class for the SDL_Window class.
bool fake_screen_
Definition: video.hpp:267
point resolution()
Definition: general.cpp:392
void update_framebuffer()
Updates and ensures the framebuffer surface is valid.
Definition: video.cpp:187
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:270
void clear_screen()
Clear the screen contents.
Definition: video.cpp:344
static map_location::DIRECTION n
std::shared_ptr< halo_record > handle
Definition: halo.hpp:29
int y
y coordinate.
Definition: point.hpp:47
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:425
bool window_has_flags(uint32_t flags) const
Tests whether the given flags are currently set on the SDL window.
Definition: video.cpp:377
virtual void join_global()
Definition: events.cpp:362
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:386
void clear_help_string(int handle)
Removes the help string with the given handle.
Definition: video.cpp:529
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.