💿🐜 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.

580 lines
15 KiB

  1. /*
  2. * Copyright (C) 2021 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 "application.hpp"
  20. #include "debug/logger.hpp"
  21. #include "event/event-dispatcher.hpp"
  22. #include "event/window-events.hpp"
  23. #include "input/scancode.hpp"
  24. #include "input/sdl-game-controller-tables.hpp"
  25. #include "input/sdl-scancode-table.hpp"
  26. #include "resources/image.hpp"
  27. #include <SDL2/SDL.h>
  28. #include <glad/glad.h>
  29. #include <stdexcept>
  30. #include <utility>
  31. #include <iostream>
  32. #include <iomanip>
  33. application::application():
  34. closed(false),
  35. fullscreen(true),
  36. v_sync(false),
  37. cursor_visible(true),
  38. display_dimensions({0, 0}),
  39. display_dpi(0.0f),
  40. window_dimensions({0, 0}),
  41. viewport_dimensions({0, 0}),
  42. mouse_position({0, 0}),
  43. logger(nullptr),
  44. sdl_window(nullptr),
  45. sdl_gl_context(nullptr)
  46. {
  47. // Setup logging
  48. logger = new debug::logger();
  49. // Get SDL compiled version
  50. SDL_version sdl_compiled_version;
  51. SDL_VERSION(&sdl_compiled_version);
  52. std::string sdl_compiled_version_string = std::to_string(sdl_compiled_version.major) + "." + std::to_string(sdl_compiled_version.minor) + "." + std::to_string(sdl_compiled_version.patch);
  53. logger->log("Compiled against SDL " + sdl_compiled_version_string);
  54. // Get SDL linked version
  55. SDL_version sdl_linked_version;
  56. SDL_GetVersion(&sdl_linked_version);
  57. std::string sdl_linked_version_string = std::to_string(sdl_linked_version.major) + "." + std::to_string(sdl_linked_version.minor) + "." + std::to_string(sdl_linked_version.patch);
  58. logger->log("Linking against SDL " + sdl_linked_version_string);
  59. // Init SDL
  60. logger->push_task("Initializing SDL");
  61. if (SDL_Init(SDL_INIT_VIDEO) != 0)
  62. {
  63. logger->pop_task(EXIT_FAILURE);
  64. throw std::runtime_error("Failed to initialize SDL");
  65. }
  66. else
  67. {
  68. logger->pop_task(EXIT_SUCCESS);
  69. }
  70. // Load default OpenGL library
  71. logger->push_task("Loading OpenGL library");
  72. if (SDL_GL_LoadLibrary(nullptr) != 0)
  73. {
  74. logger->pop_task(EXIT_FAILURE);
  75. }
  76. else
  77. {
  78. logger->pop_task(EXIT_SUCCESS);
  79. }
  80. // Set window creation hints
  81. SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  82. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  83. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
  84. SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
  85. SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  86. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  87. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  88. // Get display dimensions
  89. SDL_DisplayMode sdl_desktop_display_mode;
  90. if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0)
  91. {
  92. logger->error("Failed to detect desktop display mode: \"" + std::string(SDL_GetError()) + "\"");
  93. throw std::runtime_error("Failed to detect desktop display mode");
  94. }
  95. else
  96. {
  97. display_dimensions = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
  98. }
  99. // Get display DPI
  100. if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0)
  101. {
  102. logger->error("Failed to detect display DPI: \"" + std::string(SDL_GetError()) + "\"");
  103. throw std::runtime_error("Failed to detect display DPI");
  104. }
  105. else
  106. {
  107. logger->log("Detected " + std::to_string(sdl_desktop_display_mode.w) + "x" + std::to_string(sdl_desktop_display_mode.h) + " display with " + std::to_string(display_dpi) + " DPI");
  108. }
  109. // Create a hidden fullscreen window
  110. logger->push_task("Creating " + std::to_string(display_dimensions[0]) + "x" + std::to_string(display_dimensions[1]) + " window");
  111. sdl_window = SDL_CreateWindow
  112. (
  113. "Antkeeper",
  114. SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
  115. display_dimensions[0], display_dimensions[1],
  116. SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN
  117. );
  118. if (!sdl_window)
  119. {
  120. logger->pop_task(EXIT_FAILURE);
  121. throw std::runtime_error("Failed to create SDL window");
  122. }
  123. else
  124. {
  125. logger->pop_task(EXIT_SUCCESS);
  126. }
  127. // Create OpenGL context
  128. logger->push_task("Creating OpenGL 3.3 context");
  129. sdl_gl_context = SDL_GL_CreateContext(sdl_window);
  130. if (!sdl_gl_context)
  131. {
  132. logger->pop_task(EXIT_FAILURE);
  133. throw std::runtime_error("Failed to create OpenGL context");
  134. }
  135. else
  136. {
  137. logger->pop_task(EXIT_SUCCESS);
  138. }
  139. // Make OpenGL context current
  140. logger->push_task("Making OpenGL context current");
  141. if (SDL_GL_MakeCurrent(sdl_window, sdl_gl_context) != 0)
  142. {
  143. logger->pop_task(EXIT_FAILURE);
  144. throw std::runtime_error("Failed to make OpenGL context current");
  145. }
  146. else
  147. {
  148. logger->pop_task(EXIT_SUCCESS);
  149. }
  150. // Load OpenGL functions via GLAD
  151. logger->push_task("Loading OpenGL functions");
  152. if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
  153. {
  154. logger->pop_task(EXIT_FAILURE);
  155. throw std::runtime_error("Failed to load OpenGL functions");
  156. }
  157. else
  158. {
  159. logger->pop_task(EXIT_SUCCESS);
  160. }
  161. // Update window size and viewport size
  162. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  163. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  164. // Set v-sync mode
  165. set_v_sync(true);
  166. // Init SDL joystick and gamepad subsystems
  167. logger->push_task("Initializing SDL Joystick and Game Controller subsystems");
  168. if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
  169. {
  170. logger->pop_task(EXIT_FAILURE);
  171. }
  172. else
  173. {
  174. logger->pop_task(EXIT_SUCCESS);
  175. }
  176. // Setup rasterizer
  177. rasterizer = new gl::rasterizer();
  178. // Setup events
  179. event_dispatcher = new ::event_dispatcher();
  180. // Setup input
  181. keyboard = new input::keyboard();
  182. keyboard->set_event_dispatcher(event_dispatcher);
  183. mouse = new input::mouse();
  184. mouse->set_event_dispatcher(event_dispatcher);
  185. // Connect gamepads
  186. process_events();
  187. }
  188. application::~application()
  189. {
  190. // Destroy the OpenGL context
  191. SDL_GL_DeleteContext(sdl_gl_context);
  192. // Destroy the SDL window
  193. SDL_DestroyWindow(sdl_window);
  194. // Shutdown SDL
  195. SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
  196. SDL_Quit();
  197. }
  198. void application::close()
  199. {
  200. closed = true;
  201. }
  202. void application::set_title(const std::string& title)
  203. {
  204. SDL_SetWindowTitle(sdl_window, title.c_str());
  205. }
  206. void application::set_cursor_visible(bool visible)
  207. {
  208. SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE);
  209. cursor_visible = visible;
  210. }
  211. void application::set_relative_mouse_mode(bool enabled)
  212. {
  213. if (enabled)
  214. {
  215. SDL_GetMouseState(&mouse_position[0], &mouse_position[1]);
  216. SDL_ShowCursor(SDL_DISABLE);
  217. SDL_SetRelativeMouseMode(SDL_TRUE);
  218. }
  219. else
  220. {
  221. SDL_SetRelativeMouseMode(SDL_FALSE);
  222. SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]);
  223. if (cursor_visible)
  224. SDL_ShowCursor(SDL_ENABLE);
  225. }
  226. }
  227. void application::resize_window(int width, int height)
  228. {
  229. int x = (display_dimensions[0] >> 1) - (width >> 1);
  230. int y = (display_dimensions[1] >> 1) - (height >> 1);
  231. // Resize and center window
  232. SDL_SetWindowPosition(sdl_window, x, y);
  233. SDL_SetWindowSize(sdl_window, width, height);
  234. window_resized();
  235. }
  236. void application::set_fullscreen(bool fullscreen)
  237. {
  238. if (this->fullscreen != fullscreen)
  239. {
  240. this->fullscreen = fullscreen;
  241. if (fullscreen)
  242. {
  243. SDL_HideWindow(sdl_window);
  244. SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
  245. SDL_ShowWindow(sdl_window);
  246. }
  247. else
  248. {
  249. SDL_SetWindowFullscreen(sdl_window, 0);
  250. SDL_SetWindowBordered(sdl_window, SDL_TRUE);
  251. SDL_SetWindowResizable(sdl_window, SDL_TRUE);
  252. }
  253. }
  254. }
  255. void application::set_v_sync(bool v_sync)
  256. {
  257. if (this->v_sync != v_sync)
  258. {
  259. if (v_sync)
  260. {
  261. logger->push_task("Enabling adaptive v-sync");
  262. if (SDL_GL_SetSwapInterval(-1) != 0)
  263. {
  264. logger->pop_task(EXIT_FAILURE);
  265. logger->push_task("Enabling synchronized v-sync");
  266. if (SDL_GL_SetSwapInterval(-1) != 0)
  267. {
  268. logger->pop_task(EXIT_FAILURE);
  269. }
  270. else
  271. {
  272. this->v_sync = v_sync;
  273. logger->pop_task(EXIT_SUCCESS);
  274. }
  275. }
  276. else
  277. {
  278. this->v_sync = v_sync;
  279. logger->pop_task(EXIT_SUCCESS);
  280. }
  281. }
  282. else
  283. {
  284. logger->push_task("Disabling v-sync");
  285. if (SDL_GL_SetSwapInterval(0) != 0)
  286. {
  287. logger->pop_task(EXIT_FAILURE);
  288. }
  289. else
  290. {
  291. this->v_sync = v_sync;
  292. logger->pop_task(EXIT_SUCCESS);
  293. }
  294. }
  295. }
  296. }
  297. void application::set_window_opacity(float opacity)
  298. {
  299. SDL_SetWindowOpacity(sdl_window, opacity);
  300. }
  301. void application::swap_buffers()
  302. {
  303. SDL_GL_SwapWindow(sdl_window);
  304. }
  305. void application::show_window()
  306. {
  307. SDL_ShowWindow(sdl_window);
  308. //SDL_GL_MakeCurrent(sdl_window, sdl_gl_context);
  309. }
  310. void application::hide_window()
  311. {
  312. SDL_HideWindow(sdl_window);
  313. }
  314. void application::add_game_controller_mappings(const void* mappings, std::size_t size)
  315. {
  316. logger->push_task("Adding SDL game controller mappings");
  317. int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, size), 0);
  318. if (mapping_count == -1)
  319. {
  320. logger->pop_task(EXIT_FAILURE);
  321. }
  322. else
  323. {
  324. logger->log("Added " + std::to_string(mapping_count) + " SDL game controller mappings");
  325. logger->pop_task(EXIT_SUCCESS);
  326. }
  327. }
  328. void application::process_events()
  329. {
  330. // Mouse motion event accumulators
  331. bool mouse_motion = false;
  332. int mouse_x;
  333. int mouse_y;
  334. int mouse_dx = 0;
  335. int mouse_dy = 0;
  336. SDL_Event sdl_event;
  337. while (SDL_PollEvent(&sdl_event))
  338. {
  339. switch (sdl_event.type)
  340. {
  341. case SDL_KEYDOWN:
  342. case SDL_KEYUP:
  343. {
  344. if (sdl_event.key.repeat == 0)
  345. {
  346. input::scancode scancode = input::scancode::unknown;
  347. if (sdl_event.key.keysym.scancode <= SDL_SCANCODE_APP2)
  348. {
  349. scancode = input::sdl_scancode_table[sdl_event.key.keysym.scancode];
  350. }
  351. if (sdl_event.type == SDL_KEYDOWN)
  352. keyboard->press(scancode);
  353. else
  354. keyboard->release(scancode);
  355. }
  356. break;
  357. }
  358. case SDL_MOUSEMOTION:
  359. {
  360. // More than one mouse motion event is often generated per frame, and may be a source of lag.
  361. // Mouse events are accumulated here to prevent excess function calls and allocations
  362. mouse_motion = true;
  363. mouse_x = sdl_event.motion.x;
  364. mouse_y = sdl_event.motion.y;
  365. mouse_dx += sdl_event.motion.xrel;
  366. mouse_dy += sdl_event.motion.yrel;
  367. break;
  368. }
  369. case SDL_MOUSEBUTTONDOWN:
  370. {
  371. mouse->press(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  372. break;
  373. }
  374. case SDL_MOUSEBUTTONUP:
  375. {
  376. mouse->release(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  377. break;
  378. }
  379. case SDL_MOUSEWHEEL:
  380. {
  381. int direction = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1;
  382. mouse->scroll(sdl_event.wheel.x * direction, sdl_event.wheel.y * direction);
  383. break;
  384. }
  385. case SDL_CONTROLLERBUTTONDOWN:
  386. {
  387. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  388. {
  389. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  390. {
  391. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  392. it->second->press(button);
  393. }
  394. }
  395. break;
  396. }
  397. case SDL_CONTROLLERBUTTONUP:
  398. {
  399. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  400. {
  401. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  402. {
  403. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  404. it->second->release(button);
  405. }
  406. }
  407. break;
  408. }
  409. case SDL_CONTROLLERAXISMOTION:
  410. {
  411. if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
  412. {
  413. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  414. {
  415. input::gamepad_axis axis = input::sdl_axis_table[sdl_event.caxis.axis];
  416. float value = sdl_event.caxis.value;
  417. static const float min = static_cast<float>(std::numeric_limits<std::int16_t>::min());
  418. static const float max = static_cast<float>(std::numeric_limits<std::int16_t>::max());
  419. value /= (value < 0.0f) ? -min : max;
  420. it->second->move(axis, value);
  421. }
  422. }
  423. break;
  424. }
  425. case SDL_CONTROLLERDEVICEADDED:
  426. {
  427. if (SDL_IsGameController(sdl_event.cdevice.which))
  428. {
  429. SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
  430. std::string controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  431. if (sdl_controller)
  432. {
  433. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  434. {
  435. logger->log("Reconnected gamepad \"" + controller_name + "\"");
  436. it->second->connect(true);
  437. }
  438. else
  439. {
  440. // Get gamepad GUID
  441. SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
  442. SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
  443. char guid_buffer[33];
  444. std::memset(guid_buffer, '\0', 33);
  445. SDL_JoystickGetGUIDString(sdl_guid, &guid_buffer[0], 33);
  446. std::string guid(guid_buffer);
  447. logger->log("Connected gamepad \"" + controller_name + "\" with GUID: " + guid + "");
  448. // Create new gamepad
  449. input::gamepad* gamepad = new input::gamepad();
  450. gamepad->set_event_dispatcher(event_dispatcher);
  451. gamepad->set_guid(guid);
  452. // Add gamepad to list and map
  453. gamepads.push_back(gamepad);
  454. gamepad_map[sdl_event.cdevice.which] = gamepad;
  455. // Send controller connected event
  456. gamepad->connect(false);
  457. }
  458. }
  459. else
  460. {
  461. logger->error("Failed to connected gamepad \"" + controller_name + "\"");
  462. }
  463. }
  464. break;
  465. }
  466. case SDL_CONTROLLERDEVICEREMOVED:
  467. {
  468. SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
  469. if (sdl_controller)
  470. {
  471. SDL_GameControllerClose(sdl_controller);
  472. logger->log("Disconnected gamepad");
  473. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  474. {
  475. it->second->disconnect();
  476. }
  477. }
  478. break;
  479. }
  480. case SDL_WINDOWEVENT:
  481. {
  482. if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED)
  483. {
  484. window_resized();
  485. }
  486. break;
  487. }
  488. case SDL_QUIT:
  489. {
  490. close();
  491. break;
  492. }
  493. }
  494. }
  495. // Process accumulated mouse motion events
  496. if (mouse_motion)
  497. {
  498. mouse->move(mouse_x, mouse_y, mouse_dx, mouse_dy);
  499. }
  500. }
  501. void application::window_resized()
  502. {
  503. // Update window size and viewport size
  504. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  505. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  506. rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
  507. window_resized_event event;
  508. event.w = window_dimensions[0];
  509. event.h = window_dimensions[1];
  510. event_dispatcher->queue(event);
  511. }