The Battle for Wesnoth  1.15.0-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  image::set_pixel_format(frameBuffer->format);
184 }
185 
186 void CVideo::make_test_fake(const unsigned width, const unsigned height)
187 {
188 #if SDL_VERSION_ATLEAST(2, 0, 6)
189  frameBuffer = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_BGR888);
190 #else
191  frameBuffer = SDL_CreateRGBSurface(0, width, height, 32, 0xFF0000, 0xFF00, 0xFF, 0);
192 #endif
193 
194  image::set_pixel_format(frameBuffer->format);
195 
196  fake_interactive = true;
197  refresh_rate_ = 1;
198 }
199 
201 {
202  if(!window) {
203  return;
204  }
205 
206  surface fb = SDL_GetWindowSurface(*window);
207  if(!frameBuffer) {
208  frameBuffer = fb;
209  } else {
210  if(sdl_get_version() >= version_info(2, 0, 6)) {
211  // Because SDL has already freed the old framebuffer,
212  // ensure that we won't attempt to free it.
213  frameBuffer.clear_without_free();
214  }
215 
216  frameBuffer.assign(fb);
217  }
218 }
219 
221 {
222  // Position
223  const int x = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
224  const int y = preferences::fullscreen() ? SDL_WINDOWPOS_UNDEFINED : SDL_WINDOWPOS_CENTERED;
225 
226  // Dimensions
227  const point res = preferences::resolution();
228  const int w = res.x;
229  const int h = res.y;
230 
231  uint32_t window_flags = 0;
232 
233  // Add any more default flags here
234  window_flags |= SDL_WINDOW_RESIZABLE;
235 #ifdef __APPLE__
236  window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
237 #endif
238 
240  window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
241  } else if(preferences::maximized()) {
242  window_flags |= SDL_WINDOW_MAXIMIZED;
243  }
244 
245  // Initialize window
246  window.reset(new sdl::window("", x, y, w, h, window_flags, SDL_RENDERER_SOFTWARE));
247 
248  std::cerr << "Setting mode to " << w << "x" << h << std::endl;
249 
251 
252  SDL_DisplayMode currentDisplayMode;
253  SDL_GetCurrentDisplayMode(window->get_display_index(), &currentDisplayMode);
254  refresh_rate_ = currentDisplayMode.refresh_rate != 0 ? currentDisplayMode.refresh_rate : 60;
255 
257 
259  if(frameBuffer) {
260  image::set_pixel_format(frameBuffer->format);
261  }
262 }
263 
265 {
266  assert(window);
267  if(fake_screen_) {
268  return;
269  }
270 
271  switch(mode) {
272  case TO_FULLSCREEN:
273  window->full_screen();
274  break;
275 
276  case TO_WINDOWED:
277  window->to_window();
278  window->restore();
279  break;
280 
281  case TO_MAXIMIZED_WINDOW:
282  window->to_window();
283  window->maximize();
284  break;
285 
286  case TO_RES:
287  window->restore();
288  window->set_size(size.x, size.y);
289  window->center();
290  break;
291  }
292 
294  if(frameBuffer) {
295  image::set_pixel_format(frameBuffer->format);
296  }
297 }
298 
299 SDL_Rect CVideo::screen_area(bool as_pixels) const
300 {
301  if(!window) {
302  return {0, 0, frameBuffer->w, frameBuffer->h};
303  }
304 
305  // First, get the renderer size in pixels.
306  SDL_Point size = window->get_output_size();
307 
308  // Then convert the dimensions into screen coordinates, if applicable.
309  if(!as_pixels) {
310  float scale_x, scale_y;
311  std::tie(scale_x, scale_y) = get_dpi_scale_factor();
312 
313  size.x /= scale_x;
314  size.y /= scale_y;
315  }
316 
317  return {0, 0, size.x, size.y};
318 }
319 
320 int CVideo::get_width(bool as_pixels) const
321 {
322  return screen_area(as_pixels).w;
323 }
324 
325 int CVideo::get_height(bool as_pixels) const
326 {
327  return screen_area(as_pixels).h;
328 }
329 
330 void CVideo::delay(unsigned int milliseconds)
331 {
332  if(!game_config::no_delay) {
333  SDL_Delay(milliseconds);
334  }
335 }
336 
338 {
339  if(fake_screen_ || flip_locked_ > 0) {
340  return;
341  }
342 
343  if(window) {
344  window->render();
345  }
346 }
347 
348 void CVideo::lock_updates(bool value)
349 {
350  if(value == true) {
351  ++updated_locked_;
352  } else {
353  --updated_locked_;
354  }
355 }
356 
358 {
359  return updated_locked_ > 0;
360 }
361 
362 void CVideo::set_window_title(const std::string& title)
363 {
364  assert(window);
365  window->set_title(title);
366 }
367 
369 {
370  assert(window);
371  window->set_icon(icon);
372 }
373 
375 {
376  if(!window) {
377  return;
378  }
379 
380  window->fill(0, 0, 0, 255);
381 }
382 
384 {
385  return window.get();
386 }
387 
388 bool CVideo::window_has_flags(uint32_t flags) const
389 {
390  if(!window) {
391  return false;
392  }
393 
394  return (window->get_flags() & flags) != 0;
395 }
396 
397 std::pair<float, float> CVideo::get_dpi_scale_factor() const
398 {
399  std::pair<float, float> result{1.0f, 1.0f};
400 
401  if(!window) {
402  return result;
403  }
404 
405  float hdpi, vdpi;
406  SDL_GetDisplayDPI(window->get_display_index(), nullptr, &hdpi, &vdpi);
407 
408  result.first = hdpi / MAGIC_DPI_SCALE_NUMBER;
409  result.second = vdpi / MAGIC_DPI_SCALE_NUMBER;
410 
411  return result;
412 }
413 
414 std::vector<point> CVideo::get_available_resolutions(const bool include_current)
415 {
416  std::vector<point> result;
417 
418  if(!window) {
419  return result;
420  }
421 
422  const int display_index = window->get_display_index();
423 
424  const int modes = SDL_GetNumDisplayModes(display_index);
425  if(modes <= 0) {
426  std::cerr << "No modes supported\n";
427  return result;
428  }
429 
431 
432 #if 0
433  // DPI scale factor.
434  float scale_h, scale_v;
435  std::tie(scale_h, scale_v) = get_dpi_scale_factor();
436 #endif
437 
438  // The maximum size to which this window can be set. For some reason this won't
439  // pop up as a display mode of its own.
440  SDL_Rect bounds;
441  SDL_GetDisplayBounds(display_index, &bounds);
442 
443  SDL_DisplayMode mode;
444 
445  for(int i = 0; i < modes; ++i) {
446  if(SDL_GetDisplayMode(display_index, i, &mode) == 0) {
447  // Exclude any results outside the range of the current DPI.
448  if(mode.w > bounds.w && mode.h > bounds.h) {
449  continue;
450  }
451 
452  if(mode.w >= min_res.x && mode.h >= min_res.y) {
453  result.emplace_back(mode.w, mode.h);
454  }
455  }
456  }
457 
458  if(std::find(result.begin(), result.end(), min_res) == result.end()) {
459  result.push_back(min_res);
460  }
461 
462  if(include_current) {
463  result.push_back(current_resolution());
464  }
465 
466  std::sort(result.begin(), result.end());
467  result.erase(std::unique(result.begin(), result.end()), result.end());
468 
469  return result;
470 }
471 
473 {
474  return frameBuffer;
475 }
476 
478 {
479  return point(window->get_size()); // Convert from plain SDL_Point
480 }
481 
483 {
484  return (window->get_flags() & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
485 }
486 
487 int CVideo::set_help_string(const std::string& str)
488 {
490 
491  const color_t color{0, 0, 0, 0xbb};
492 
493  int size = font::SIZE_LARGE;
494 
495  while(size > 0) {
496  if(font::line_width(str, size) > get_width()) {
497  size--;
498  } else {
499  break;
500  }
501  }
502 
503  const int border = 5;
504 
505  font::floating_label flabel(str);
506  flabel.set_font_size(size);
507  flabel.set_position(get_width() / 2, get_height());
508  flabel.set_bg_color(color);
509  flabel.set_border_size(border);
510 
512 
513  const SDL_Rect& rect = font::get_floating_label_rect(help_string_);
514  font::move_floating_label(help_string_, 0.0, -double(rect.h));
515 
516  return help_string_;
517 }
518 
520 {
521  if(handle == help_string_) {
523  help_string_ = 0;
524  }
525 }
526 
528 {
530 }
531 
532 void CVideo::set_fullscreen(bool ison)
533 {
534  if(window && is_fullscreen() != ison) {
535  const point& res = preferences::resolution();
536 
537  MODE_EVENT mode;
538 
539  if(ison) {
540  mode = TO_FULLSCREEN;
541  } else {
543  }
544 
545  set_window_mode(mode, res);
546 
547  if(display* d = display::get_singleton()) {
548  d->redraw_everything();
549  }
550  }
551 
552  // Change the config value.
554 }
555 
557 {
559 }
560 
561 bool CVideo::set_resolution(const unsigned width, const unsigned height)
562 {
563  return set_resolution(point(width, height));
564 }
565 
567 {
568  if(resolution == current_resolution()) {
569  return false;
570  }
571 
572  set_window_mode(TO_RES, resolution);
573 
574  if(display* d = display::get_singleton()) {
575  d->redraw_everything();
576  }
577 
578  // Change the saved values in preferences.
579  preferences::_set_resolution(resolution);
581 
582  // Push a window-resized event to the queue. This is necessary so various areas
583  // of the game (like GUI2) update properly with the new size.
585 
586  return true;
587 }
588 
589 void CVideo::lock_flips(bool lock)
590 {
591  if(lock) {
592  ++flip_locked_;
593  } else {
594  --flip_locked_;
595  }
596 }
void raise_resize_event()
Definition: events.cpp:714
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:368
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:357
point current_resolution()
Definition: video.cpp:477
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_pixel_format(SDL_PixelFormat *format)
sets the pixel format used by the images.
Definition: picture.cpp:814
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:397
#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:337
void lock_updates(bool value)
Stop the screen being redrawn.
Definition: video.cpp:348
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:472
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:561
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:362
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:186
void lock_flips(bool)
Definition: video.cpp:589
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:383
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:320
#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:220
void toggle_fullscreen()
Definition: video.cpp:556
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:532
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:325
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:487
const int SIZE_LARGE
Definition: constants.cpp:27
#define MAGIC_DPI_SCALE_NUMBER
Definition: video.cpp:35
void clear_without_free()
Definition: surface.hpp:71
bool is_fullscreen() const
Definition: video.cpp:482
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:527
static void delay(unsigned int milliseconds)
Waits a given number of milliseconds before returning.
Definition: video.cpp:330
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:264
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:200
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:299
void clear_screen()
Clear the screen contents.
Definition: video.cpp:374
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:414
bool window_has_flags(uint32_t flags) const
Tests whether the given flags are currently set on the SDL window.
Definition: video.cpp:388
virtual void join_global()
Definition: events.cpp:315
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:519