💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

511 lines
16 KiB

  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. #include <engine/app/sdl/sdl-window-manager.hpp>
  20. #include <engine/app/sdl/sdl-window.hpp>
  21. #include <engine/debug/log.hpp>
  22. #include <engine/config.hpp>
  23. #include <engine/gl/pipeline.hpp>
  24. #include <stdexcept>
  25. namespace app {
  26. sdl_window_manager::sdl_window_manager()
  27. {
  28. // Init SDL events and video subsystems
  29. debug::log_trace("Initializing SDL events and video subsystems...");
  30. if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
  31. {
  32. debug::log_fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
  33. throw std::runtime_error("Failed to initialize SDL events and video subsystems");
  34. }
  35. debug::log_trace("Initialized SDL events and video subsystems");
  36. // Query displays
  37. const int display_count = SDL_GetNumVideoDisplays();
  38. if (display_count < 1)
  39. {
  40. debug::log_warning("No displays detected: {}", SDL_GetError());
  41. }
  42. else
  43. {
  44. // Allocate displays
  45. m_displays.resize(display_count);
  46. debug::log_info("Display count: {}", display_count);
  47. for (int i = 0; i < display_count; ++i)
  48. {
  49. // Update display state
  50. update_display(i);
  51. // Log display information
  52. display& display = m_displays[i];
  53. const auto display_resolution = display.get_bounds().size();
  54. 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());
  55. }
  56. }
  57. // Load OpenGL library
  58. debug::log_trace("Loading OpenGL library...");
  59. if (SDL_GL_LoadLibrary(nullptr) != 0)
  60. {
  61. debug::log_fatal("Failed to load OpenGL library: {}", SDL_GetError());
  62. throw std::runtime_error("Failed to load OpenGL library");
  63. }
  64. debug::log_trace("Loaded OpenGL library");
  65. // Set OpenGL-related window creation hints
  66. SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  67. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  68. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
  69. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
  70. #if defined(DEBUG)
  71. SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG | SDL_GL_CONTEXT_DEBUG_FLAG);
  72. #else
  73. SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
  74. #endif
  75. SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  76. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
  77. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
  78. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
  79. SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
  80. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
  81. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
  82. }
  83. sdl_window_manager::~sdl_window_manager()
  84. {
  85. // Quit SDL video subsystem
  86. debug::log_trace("Quitting SDL video subsystem...");
  87. SDL_QuitSubSystem(SDL_INIT_VIDEO);
  88. debug::log_trace("Quit SDL video subsystem");
  89. }
  90. std::shared_ptr<window> sdl_window_manager::create_window
  91. (
  92. const std::string& title,
  93. const math::ivec2& windowed_position,
  94. const math::ivec2& windowed_size,
  95. bool maximized,
  96. bool fullscreen,
  97. bool v_sync
  98. )
  99. {
  100. // Create new window
  101. std::shared_ptr<app::sdl_window> window = std::make_shared<app::sdl_window>
  102. (
  103. title,
  104. windowed_position,
  105. windowed_size,
  106. maximized,
  107. fullscreen,
  108. v_sync
  109. );
  110. // Map internal SDL window to window
  111. m_window_map[window->m_internal_window] = window.get();
  112. return window;
  113. }
  114. void sdl_window_manager::update()
  115. {
  116. // Gather SDL events from event queue
  117. SDL_PumpEvents();
  118. for (;;)
  119. {
  120. // Get next window or display event
  121. SDL_Event event;
  122. int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DISPLAYEVENT, SDL_SYSWMEVENT);
  123. if (!status)
  124. {
  125. break;
  126. }
  127. else if (status < 0)
  128. {
  129. debug::log_error("Failed to peep SDL events: {}", SDL_GetError());
  130. throw std::runtime_error("Failed to peep SDL events");
  131. }
  132. // Handle event
  133. if (event.type == SDL_WINDOWEVENT)
  134. {
  135. switch (event.window.event)
  136. {
  137. case SDL_WINDOWEVENT_SIZE_CHANGED:
  138. {
  139. // Get window
  140. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  141. app::sdl_window* window = get_window(internal_window);
  142. // Update window state
  143. window->m_size = {event.window.data1, event.window.data2};
  144. const auto window_flags = SDL_GetWindowFlags(internal_window);
  145. if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)))
  146. {
  147. window->m_windowed_size = window->m_size;
  148. }
  149. SDL_GL_GetDrawableSize(internal_window, &window->m_viewport_size.x(), &window->m_viewport_size.y());
  150. // Change reported dimensions of graphics pipeline default framebuffer
  151. window->m_graphics_pipeline->defaut_framebuffer_resized
  152. (
  153. static_cast<std::uint32_t>(window->m_viewport_size.x()),
  154. static_cast<std::uint32_t>(window->m_viewport_size.y())
  155. );
  156. // Log window resized event
  157. debug::log_debug("Window {} resized to {}x{}", event.window.windowID, event.window.data1, event.window.data2);
  158. // Publish window resized event
  159. window->m_resized_publisher.publish({window, window->m_size});
  160. break;
  161. }
  162. case SDL_WINDOWEVENT_MOVED:
  163. {
  164. // Get window
  165. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  166. app::sdl_window* window = get_window(internal_window);
  167. // Update window state
  168. window->m_position = {event.window.data1, event.window.data2};
  169. const auto window_flags = SDL_GetWindowFlags(internal_window);
  170. if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)))
  171. {
  172. window->m_windowed_position = window->m_position;
  173. }
  174. // Log window moved event
  175. debug::log_debug("Window {} moved to ({}, {})", event.window.windowID, event.window.data1, event.window.data2);
  176. // Publish window moved event
  177. window->m_moved_publisher.publish({window, window->m_position});
  178. break;
  179. }
  180. case SDL_WINDOWEVENT_FOCUS_GAINED:
  181. {
  182. // Get window
  183. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  184. app::sdl_window* window = get_window(internal_window);
  185. // Log window focus gained event
  186. debug::log_debug("Window {} gained focus", event.window.windowID);
  187. // Publish window focus gained event
  188. window->m_focus_changed_publisher.publish({window, true});
  189. break;
  190. }
  191. case SDL_WINDOWEVENT_FOCUS_LOST:
  192. {
  193. // Get window
  194. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  195. app::sdl_window* window = get_window(internal_window);
  196. // Log window focus lost event
  197. debug::log_debug("Window {} lost focus", event.window.windowID);
  198. // Publish window focus lost event
  199. window->m_focus_changed_publisher.publish({window, false});
  200. break;
  201. }
  202. case SDL_WINDOWEVENT_MAXIMIZED:
  203. {
  204. // Get window
  205. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  206. app::sdl_window* window = get_window(internal_window);
  207. // Update window state
  208. window->m_maximized = true;
  209. // Log window focus gained event
  210. debug::log_debug("Window {} maximized", event.window.windowID);
  211. // Publish window maximized event
  212. window->m_maximized_publisher.publish({window});
  213. break;
  214. }
  215. case SDL_WINDOWEVENT_RESTORED:
  216. {
  217. // Get window
  218. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  219. app::sdl_window* window = get_window(internal_window);
  220. // Update window state
  221. window->m_maximized = false;
  222. // Log window restored event
  223. debug::log_debug("Window {} restored", event.window.windowID);
  224. // Publish window restored event
  225. window->m_restored_publisher.publish({window});
  226. break;
  227. }
  228. case SDL_WINDOWEVENT_MINIMIZED:
  229. {
  230. // Get window
  231. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  232. app::sdl_window* window = get_window(internal_window);
  233. // Log window focus gained event
  234. debug::log_debug("Window {} minimized", event.window.windowID);
  235. // Publish window minimized event
  236. window->m_minimized_publisher.publish({window});
  237. break;
  238. }
  239. [[unlikely]] case SDL_WINDOWEVENT_CLOSE:
  240. {
  241. // Get window
  242. SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
  243. app::sdl_window* window = get_window(internal_window);
  244. // Log window closed event
  245. debug::log_debug("Window {} closed", event.window.windowID);
  246. // Publish window closed event
  247. window->m_closed_publisher.publish({window});
  248. break;
  249. }
  250. default:
  251. break;
  252. }
  253. }
  254. else if (event.type == SDL_DISPLAYEVENT)
  255. {
  256. switch (event.display.event)
  257. {
  258. case SDL_DISPLAYEVENT_CONNECTED:
  259. if (event.display.display < m_displays.size())
  260. {
  261. // Get previously connected display
  262. display& display = m_displays[event.display.display];
  263. // Update display state
  264. display.m_connected = true;
  265. // Log display (re)connected event
  266. debug::log_info("Reconnected display {}", event.display.display);
  267. // Publish display (re)connected event
  268. display.m_connected_publisher.publish({&display});
  269. }
  270. else if (event.display.display == m_displays.size())
  271. {
  272. // Allocate new display
  273. m_displays.resize(m_displays.size() + 1);
  274. display& display = m_displays[event.display.display];
  275. // Update display state
  276. update_display(event.display.display);
  277. // Log display info
  278. const auto display_resolution = display.get_bounds().size();
  279. 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());
  280. // Publish display connected event
  281. display.m_connected_publisher.publish({&display});
  282. }
  283. else
  284. {
  285. debug::log_error("Index of connected display ({}) out of range", event.display.display);
  286. }
  287. break;
  288. case SDL_DISPLAYEVENT_DISCONNECTED:
  289. if (event.display.display < m_displays.size())
  290. {
  291. // Get display
  292. display& display = m_displays[event.display.display];
  293. // Update display state
  294. display.m_connected = false;
  295. // Log display disconnected event
  296. debug::log_info("Disconnected display {}", event.display.display);
  297. // Publish display disconnected event
  298. display.m_disconnected_publisher.publish({&display});
  299. }
  300. else
  301. {
  302. debug::log_error("Index of disconnected display ({}) out of range", event.display.display);
  303. }
  304. break;
  305. case SDL_DISPLAYEVENT_ORIENTATION:
  306. if (event.display.display < m_displays.size())
  307. {
  308. // Get display
  309. display& display = m_displays[event.display.display];
  310. // Update display state
  311. switch (event.display.data1)
  312. {
  313. case SDL_ORIENTATION_LANDSCAPE:
  314. display.set_orientation(display_orientation::landscape);
  315. break;
  316. case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
  317. display.set_orientation(display_orientation::landscape_flipped);
  318. break;
  319. case SDL_ORIENTATION_PORTRAIT:
  320. display.set_orientation(display_orientation::portrait);
  321. break;
  322. case SDL_ORIENTATION_PORTRAIT_FLIPPED:
  323. display.set_orientation(display_orientation::portrait_flipped);
  324. break;
  325. default:
  326. display.set_orientation(display_orientation::unknown);
  327. break;
  328. }
  329. // Log display orientation changed event
  330. debug::log_info("Display {} orientation changed", event.display.display);
  331. // Publish display orientation changed event
  332. display.m_orientation_changed_publisher.publish({&display, display.get_orientation()});
  333. }
  334. else
  335. {
  336. debug::log_error("Index of orientation-changed display ({}) out of range", event.display.display);
  337. }
  338. break;
  339. default:
  340. break;
  341. }
  342. }
  343. }
  344. }
  345. sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window)
  346. {
  347. if (auto i = m_window_map.find(internal_window); i != m_window_map.end())
  348. {
  349. return i->second;
  350. }
  351. else
  352. {
  353. throw std::runtime_error("SDL window unrecognized by SDL window manager");
  354. }
  355. return nullptr;
  356. }
  357. std::size_t sdl_window_manager::get_display_count() const
  358. {
  359. return m_displays.size();
  360. }
  361. const display& sdl_window_manager::get_display(std::size_t index) const
  362. {
  363. return m_displays[index];
  364. }
  365. void sdl_window_manager::update_display(int sdl_display_index)
  366. {
  367. // Query display mode
  368. SDL_DisplayMode sdl_display_mode;
  369. if (SDL_GetDesktopDisplayMode(sdl_display_index, &sdl_display_mode) != 0)
  370. {
  371. debug::log_error("Failed to get mode of display {}: {}", sdl_display_index, SDL_GetError());
  372. SDL_ClearError();
  373. sdl_display_mode = {0, 0, 0, 0, nullptr};
  374. }
  375. // Query display name
  376. const char* sdl_display_name = SDL_GetDisplayName(sdl_display_index);
  377. if (!sdl_display_name)
  378. {
  379. debug::log_warning("Failed to get name of display {}: {}", sdl_display_index, SDL_GetError());
  380. SDL_ClearError();
  381. sdl_display_name = nullptr;
  382. }
  383. // Query display bounds
  384. SDL_Rect sdl_display_bounds;
  385. if (SDL_GetDisplayBounds(sdl_display_index, &sdl_display_bounds) != 0)
  386. {
  387. debug::log_warning("Failed to get bounds of display {}: {}", sdl_display_index, SDL_GetError());
  388. SDL_ClearError();
  389. sdl_display_bounds = {0, 0, sdl_display_mode.w, sdl_display_mode.h};
  390. }
  391. // Query display usable bounds
  392. SDL_Rect sdl_display_usable_bounds;
  393. if (SDL_GetDisplayUsableBounds(sdl_display_index, &sdl_display_usable_bounds) != 0)
  394. {
  395. debug::log_warning("Failed to get usable bounds of display {}: {}", sdl_display_index, SDL_GetError());
  396. SDL_ClearError();
  397. sdl_display_usable_bounds = sdl_display_bounds;
  398. }
  399. // Query display DPI
  400. float sdl_display_dpi;
  401. if (SDL_GetDisplayDPI(sdl_display_index, &sdl_display_dpi, nullptr, nullptr) != 0)
  402. {
  403. debug::log_warning("Failed to get DPI of display {}: {}", sdl_display_index, SDL_GetError());
  404. SDL_ClearError();
  405. sdl_display_dpi = 0.0f;
  406. }
  407. // Query display orientation
  408. SDL_DisplayOrientation sdl_display_orientation = SDL_GetDisplayOrientation(sdl_display_index);
  409. // Update display properties
  410. display& display = m_displays[sdl_display_index];
  411. display.set_index(static_cast<std::size_t>(sdl_display_index));
  412. display.set_name(sdl_display_name ? sdl_display_name : std::string());
  413. 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}});
  414. 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}});
  415. display.set_refresh_rate(sdl_display_mode.refresh_rate);
  416. display.set_dpi(sdl_display_dpi);
  417. switch (sdl_display_orientation)
  418. {
  419. case SDL_ORIENTATION_LANDSCAPE:
  420. display.set_orientation(display_orientation::landscape);
  421. break;
  422. case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
  423. display.set_orientation(display_orientation::landscape_flipped);
  424. break;
  425. case SDL_ORIENTATION_PORTRAIT:
  426. display.set_orientation(display_orientation::portrait);
  427. break;
  428. case SDL_ORIENTATION_PORTRAIT_FLIPPED:
  429. display.set_orientation(display_orientation::portrait_flipped);
  430. break;
  431. default:
  432. display.set_orientation(display_orientation::unknown);
  433. break;
  434. }
  435. display.m_connected = true;
  436. }
  437. } // namespace app