Antkeeper  0.0.1
sdl-window-manager.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code 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 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
22 #include <engine/debug/log.hpp>
23 #include <engine/config.hpp>
24 #include <engine/gl/pipeline.hpp>
25 #include <stdexcept>
26 
27 namespace app {
28 
30 {
31  // Init SDL events and video subsystems
32  debug::log_trace("Initializing SDL events and video subsystems...");
33  if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
34  {
35  debug::log_fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
36  throw std::runtime_error("Failed to initialize SDL events and video subsystems");
37  }
38  debug::log_trace("Initialized SDL events and video subsystems");
39 
40  // Query displays
41  const int display_count = SDL_GetNumVideoDisplays();
42  if (display_count < 1)
43  {
44  debug::log_warning("No displays detected: {}", SDL_GetError());
45  }
46  else
47  {
48  // Allocate displays
49  m_displays.resize(display_count);
50  debug::log_info("Display count: {}", display_count);
51 
52  for (int i = 0; i < display_count; ++i)
53  {
54  // Update display state
55  update_display(i);
56 
57  // Log display information
58  display& display = m_displays[i];
59  const auto display_resolution = display.get_bounds().size();
60  debug::log_info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi());
61  }
62  }
63 
64  // Load OpenGL library
65  debug::log_trace("Loading OpenGL library...");
66  if (SDL_GL_LoadLibrary(nullptr) != 0)
67  {
68  debug::log_fatal("Failed to load OpenGL library: {}", SDL_GetError());
69  throw std::runtime_error("Failed to load OpenGL library");
70  }
71  debug::log_trace("Loaded OpenGL library");
72 
73  // Set OpenGL-related window creation hints
74  SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
75  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
76  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
77  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
78 
79  #if defined(DEBUG)
80  SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG | SDL_GL_CONTEXT_DEBUG_FLAG);
81  #else
82  SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
83  #endif
84 
85  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
86  SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
87  SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
88  SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
89  SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
90  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
91  SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
92 }
93 
95 {
96  // Quit SDL video subsystem
97  debug::log_trace("Quitting SDL video subsystem...");
98  SDL_QuitSubSystem(SDL_INIT_VIDEO);
99  debug::log_trace("Quit SDL video subsystem");
100 }
101 
102 std::shared_ptr<window> sdl_window_manager::create_window
103 (
104  const std::string& title,
105  const math::ivec2& windowed_position,
106  const math::ivec2& windowed_size,
107  bool maximized,
108  bool fullscreen,
109  bool v_sync
110 )
111 {
112  // Create new window
113  std::shared_ptr<app::sdl_window> window = std::make_shared<app::sdl_window>
114  (
115  title,
116  windowed_position,
117  windowed_size,
118  maximized,
119  fullscreen,
120  v_sync
121  );
122 
123  // Map internal SDL window to window
124  m_window_map[window->m_internal_window] = window.get();
125 
126  return window;
127 }
128 
130 {
131  // Gather SDL events from event queue
132  SDL_PumpEvents();
133 
134  for (;;)
135  {
136  // Get next window or display event
137  SDL_Event event;
138  int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DISPLAYEVENT, SDL_SYSWMEVENT);
139 
140  if (!status)
141  {
142  break;
143  }
144  else if (status < 0)
145  {
146  debug::log_error("Failed to peep SDL events: {}", SDL_GetError());
147  throw std::runtime_error("Failed to peep SDL events");
148  }
149 
150  // Handle event
151  if (event.type == SDL_WINDOWEVENT)
152  {
153  switch (event.window.event)
154  {
155  case SDL_WINDOWEVENT_SIZE_CHANGED:
156  {
157  // Get window
158  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
159  app::sdl_window* window = get_window(internal_window);
160 
161  // Update window state
162  window->m_size = {event.window.data1, event.window.data2};
163  const auto window_flags = SDL_GetWindowFlags(internal_window);
164  if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)))
165  {
167  }
168  SDL_GL_GetDrawableSize(internal_window, &window->m_viewport_size.x(), &window->m_viewport_size.y());
169 
170  // Change reported dimensions of graphics pipeline default framebuffer
171  window->m_graphics_pipeline->defaut_framebuffer_resized
172  (
173  static_cast<std::uint32_t>(window->m_viewport_size.x()),
174  static_cast<std::uint32_t>(window->m_viewport_size.y())
175  );
176 
177  // Log window resized event
178  debug::log_debug("Window {} resized to {}x{}", event.window.windowID, event.window.data1, event.window.data2);
179 
180  // Publish window resized event
182  break;
183  }
184 
185  case SDL_WINDOWEVENT_MOVED:
186  {
187  // Get window
188  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
189  app::sdl_window* window = get_window(internal_window);
190 
191  // Update window state
192  window->m_position = {event.window.data1, event.window.data2};
193  const auto window_flags = SDL_GetWindowFlags(internal_window);
194  if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)))
195  {
197  }
198 
199  // Log window moved event
200  debug::log_debug("Window {} moved to ({}, {})", event.window.windowID, event.window.data1, event.window.data2);
201 
202  // Publish window moved event
204  break;
205  }
206 
207  case SDL_WINDOWEVENT_FOCUS_GAINED:
208  {
209  // Get window
210  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
211  app::sdl_window* window = get_window(internal_window);
212 
213  // Log window focus gained event
214  debug::log_debug("Window {} gained focus", event.window.windowID);
215 
216  // Publish window focus gained event
217  window->m_focus_changed_publisher.publish({window, true});
218  break;
219  }
220 
221  case SDL_WINDOWEVENT_FOCUS_LOST:
222  {
223  // Get window
224  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
225  app::sdl_window* window = get_window(internal_window);
226 
227  // Log window focus lost event
228  debug::log_debug("Window {} lost focus", event.window.windowID);
229 
230  // Publish window focus lost event
231  window->m_focus_changed_publisher.publish({window, false});
232  break;
233  }
234 
235  case SDL_WINDOWEVENT_MAXIMIZED:
236  {
237  // Get window
238  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
239  app::sdl_window* window = get_window(internal_window);
240 
241  // Update window state
242  window->m_maximized = true;
243 
244  // Log window focus gained event
245  debug::log_debug("Window {} maximized", event.window.windowID);
246 
247  // Publish window maximized event
248  window->m_maximized_publisher.publish({window});
249  break;
250  }
251 
252  case SDL_WINDOWEVENT_RESTORED:
253  {
254  // Get window
255  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
256  app::sdl_window* window = get_window(internal_window);
257 
258  // Update window state
259  window->m_maximized = false;
260 
261  // Log window restored event
262  debug::log_debug("Window {} restored", event.window.windowID);
263 
264  // Publish window restored event
265  window->m_restored_publisher.publish({window});
266  break;
267  }
268 
269  case SDL_WINDOWEVENT_MINIMIZED:
270  {
271  // Get window
272  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
273  app::sdl_window* window = get_window(internal_window);
274 
275  // Log window focus gained event
276  debug::log_debug("Window {} minimized", event.window.windowID);
277 
278  // Publish window minimized event
279  window->m_minimized_publisher.publish({window});
280  break;
281  }
282 
283  [[unlikely]] case SDL_WINDOWEVENT_CLOSE:
284  {
285  // Get window
286  SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
287  app::sdl_window* window = get_window(internal_window);
288 
289  // Log window closed event
290  debug::log_debug("Window {} closed", event.window.windowID);
291 
292  // Publish window closed event
293  window->m_closed_publisher.publish({window});
294  break;
295  }
296 
297  default:
298  break;
299  }
300  }
301  else if (event.type == SDL_DISPLAYEVENT)
302  {
303  switch (event.display.event)
304  {
305  case SDL_DISPLAYEVENT_CONNECTED:
306  if (event.display.display < m_displays.size())
307  {
308  // Get previously connected display
309  display& display = m_displays[event.display.display];
310 
311  // Update display state
312  display.m_connected = true;
313 
314  // Log display (re)connected event
315  debug::log_info("Reconnected display {}", event.display.display);
316 
317  // Publish display (re)connected event
318  display.m_connected_publisher.publish({&display});
319  }
320  else if (event.display.display == m_displays.size())
321  {
322  // Allocate new display
323  m_displays.resize(m_displays.size() + 1);
324  display& display = m_displays[event.display.display];
325 
326  // Update display state
327  update_display(event.display.display);
328 
329  // Log display info
330  const auto display_resolution = display.get_bounds().size();
331  debug::log_info("Connected display {}; name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", event.display.display, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi());
332 
333  // Publish display connected event
334  display.m_connected_publisher.publish({&display});
335  }
336  else
337  {
338  debug::log_error("Index of connected display ({}) out of range", event.display.display);
339  }
340  break;
341 
342  case SDL_DISPLAYEVENT_DISCONNECTED:
343  if (event.display.display < m_displays.size())
344  {
345  // Get display
346  display& display = m_displays[event.display.display];
347 
348  // Update display state
349  display.m_connected = false;
350 
351  // Log display disconnected event
352  debug::log_info("Disconnected display {}", event.display.display);
353 
354  // Publish display disconnected event
355  display.m_disconnected_publisher.publish({&display});
356  }
357  else
358  {
359  debug::log_error("Index of disconnected display ({}) out of range", event.display.display);
360  }
361  break;
362 
363  case SDL_DISPLAYEVENT_ORIENTATION:
364  if (event.display.display < m_displays.size())
365  {
366  // Get display
367  display& display = m_displays[event.display.display];
368 
369  // Update display state
370  switch (event.display.data1)
371  {
372  case SDL_ORIENTATION_LANDSCAPE:
374  break;
375  case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
377  break;
378  case SDL_ORIENTATION_PORTRAIT:
380  break;
381  case SDL_ORIENTATION_PORTRAIT_FLIPPED:
383  break;
384  default:
386  break;
387  }
388 
389  // Log display orientation changed event
390  debug::log_info("Display {} orientation changed", event.display.display);
391 
392  // Publish display orientation changed event
393  display.m_orientation_changed_publisher.publish({&display, display.get_orientation()});
394  }
395  else
396  {
397  debug::log_error("Index of orientation-changed display ({}) out of range", event.display.display);
398  }
399  break;
400 
401  default:
402  break;
403  }
404  }
405  }
406 }
407 
408 sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window)
409 {
410  if (auto i = m_window_map.find(internal_window); i != m_window_map.end())
411  {
412  return i->second;
413  }
414  else
415  {
416  throw std::runtime_error("SDL window unrecognized by SDL window manager");
417  }
418 
419  return nullptr;
420 }
421 
423 {
424  return m_displays.size();
425 }
426 
427 const display& sdl_window_manager::get_display(std::size_t index) const
428 {
429  return m_displays[index];
430 }
431 
432 void sdl_window_manager::update_display(int sdl_display_index)
433 {
434  // Query display mode
435  SDL_DisplayMode sdl_display_mode;
436  if (SDL_GetDesktopDisplayMode(sdl_display_index, &sdl_display_mode) != 0)
437  {
438  debug::log_error("Failed to get mode of display {}: {}", sdl_display_index, SDL_GetError());
439  SDL_ClearError();
440  sdl_display_mode = {0, 0, 0, 0, nullptr};
441  }
442 
443  // Query display name
444  const char* sdl_display_name = SDL_GetDisplayName(sdl_display_index);
445  if (!sdl_display_name)
446  {
447  debug::log_warning("Failed to get name of display {}: {}", sdl_display_index, SDL_GetError());
448  SDL_ClearError();
449  sdl_display_name = nullptr;
450  }
451 
452  // Query display bounds
453  SDL_Rect sdl_display_bounds;
454  if (SDL_GetDisplayBounds(sdl_display_index, &sdl_display_bounds) != 0)
455  {
456  debug::log_warning("Failed to get bounds of display {}: {}", sdl_display_index, SDL_GetError());
457  SDL_ClearError();
458  sdl_display_bounds = {0, 0, sdl_display_mode.w, sdl_display_mode.h};
459  }
460 
461  // Query display usable bounds
462  SDL_Rect sdl_display_usable_bounds;
463  if (SDL_GetDisplayUsableBounds(sdl_display_index, &sdl_display_usable_bounds) != 0)
464  {
465  debug::log_warning("Failed to get usable bounds of display {}: {}", sdl_display_index, SDL_GetError());
466  SDL_ClearError();
467  sdl_display_usable_bounds = sdl_display_bounds;
468  }
469 
470  // Query display DPI
471  float sdl_display_dpi;
472  if (SDL_GetDisplayDPI(sdl_display_index, &sdl_display_dpi, nullptr, nullptr) != 0)
473  {
474  debug::log_warning("Failed to get DPI of display {}: {}", sdl_display_index, SDL_GetError());
475  SDL_ClearError();
476  sdl_display_dpi = 0.0f;
477  }
478 
479  // Query display orientation
480  SDL_DisplayOrientation sdl_display_orientation = SDL_GetDisplayOrientation(sdl_display_index);
481 
482  // Update display properties
483  display& display = m_displays[sdl_display_index];
484  display.set_index(static_cast<std::size_t>(sdl_display_index));
485  display.set_name(sdl_display_name ? sdl_display_name : std::string());
486  display.set_bounds({{sdl_display_bounds.x, sdl_display_bounds.y}, {sdl_display_bounds.x + sdl_display_bounds.w, sdl_display_bounds.y + sdl_display_bounds.h}});
487  display.set_usable_bounds({{sdl_display_usable_bounds.x, sdl_display_usable_bounds.y}, {sdl_display_usable_bounds.x + sdl_display_usable_bounds.w, sdl_display_usable_bounds.y + sdl_display_usable_bounds.h}});
488  display.set_refresh_rate(sdl_display_mode.refresh_rate);
489  display.set_dpi(sdl_display_dpi);
490  switch (sdl_display_orientation)
491  {
492  case SDL_ORIENTATION_LANDSCAPE:
493  display.set_orientation(display_orientation::landscape);
494  break;
495  case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
496  display.set_orientation(display_orientation::landscape_flipped);
497  break;
498  case SDL_ORIENTATION_PORTRAIT:
499  display.set_orientation(display_orientation::portrait);
500  break;
501  case SDL_ORIENTATION_PORTRAIT_FLIPPED:
502  display.set_orientation(display_orientation::portrait_flipped);
503  break;
504  default:
505  display.set_orientation(display_orientation::unknown);
506  break;
507  }
508  display.m_connected = true;
509 }
510 
511 } // namespace app
Virtual display.
Definition: display.hpp:35
const geom::rectangle< int > & get_bounds() const noexcept
Returns the bounds of the display, in display units.
Definition: display.hpp:118
const std::string & get_name() const noexcept
Returns the name of the display.
Definition: display.hpp:112
const int & get_refresh_rate() const noexcept
Returns the refresh rate of the display, in Hz.
Definition: display.hpp:130
const display_orientation & get_orientation() const noexcept
Returns the current orientation of the display.
Definition: display.hpp:142
void set_orientation(display_orientation orientation) noexcept
Sets the orientation of the display.
Definition: display.hpp:100
const float & get_dpi() const noexcept
Returns the DPI of the display.
Definition: display.hpp:136
const display & get_display(std::size_t index) const override
Returns the display with the given index.
std::shared_ptr< window > create_window(const std::string &title, const math::ivec2 &windowed_position, const math::ivec2 &windowed_size, bool maximized, bool fullscreen, bool v_sync) override
std::size_t get_display_count() const override
Returns the number of available displays.
~sdl_window_manager() override
Destructs an SDL window manager.
sdl_window_manager()
Constructs an SDL window manager.
void update() override
Updates all managed windows.
event::publisher< window_restored_event > m_restored_publisher
Definition: window.hpp:245
event::publisher< window_maximized_event > m_maximized_publisher
Definition: window.hpp:241
event::publisher< window_closed_event > m_closed_publisher
Definition: window.hpp:239
math::ivec2 m_viewport_size
Definition: window.hpp:234
math::ivec2 m_windowed_position
Definition: window.hpp:228
math::ivec2 m_windowed_size
Definition: window.hpp:230
event::publisher< window_focus_changed_event > m_focus_changed_publisher
Definition: window.hpp:240
math::ivec2 m_size
Definition: window.hpp:231
bool m_maximized
Definition: window.hpp:235
math::ivec2 m_position
Definition: window.hpp:229
event::publisher< window_moved_event > m_moved_publisher
Definition: window.hpp:243
event::publisher< window_minimized_event > m_minimized_publisher
Definition: window.hpp:242
event::publisher< window_resized_event > m_resized_publisher
Definition: window.hpp:244
status
Behavior tree node return status enumerations.
Definition: status.hpp:28
@ landscape
Display is in landscape mode, with the right side up, relative to portrait mode.
@ unknown
Display orientation unknown.
@ portrait
Display is in portrait mode.
@ portrait_flipped
Display is in portrait mode, upside down.
@ landscape_flipped
Display is in landscape mode, with the left side up, relative to portrait mode.
log_message< log_message_severity::fatal, Args... > log_fatal
Formats and logs a fatal error message.
Definition: log.hpp:158
log_message< log_message_severity::warning, Args... > log_warning
Formats and logs a warning message.
Definition: log.hpp:130
log_message< log_message_severity::trace, Args... > log_trace
Formats and logs a trace message.
Definition: log.hpp:88
log_message< log_message_severity::debug, Args... > log_debug
Formats and logs a debug message.
Definition: log.hpp:102
log_message< log_message_severity::error, Args... > log_error
Formats and logs an error message.
Definition: log.hpp:144
log_message< log_message_severity::info, Args... > log_info
Formats and logs an info message.
Definition: log.hpp:116
Publish-subscribe messaging.
Definition: channel.hpp:32
n-dimensional vector.
Definition: vector.hpp:44
constexpr element_type & x() noexcept
Returns a reference to the first element.
Definition: vector.hpp:164
constexpr element_type & y() noexcept
Returns a reference to the second element.
Definition: vector.hpp:180