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

816 lines
20 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 "animation/frame-scheduler.hpp"
  20. #include "application.hpp"
  21. #include "debug/logger.hpp"
  22. #include "debug/performance-sampler.hpp"
  23. #include "event/event-dispatcher.hpp"
  24. #include "event/window-events.hpp"
  25. #include "input/scancode.hpp"
  26. #include "input/sdl-game-controller-tables.hpp"
  27. #include "input/sdl-scancode-table.hpp"
  28. #include "resources/image.hpp"
  29. #include <SDL2/SDL.h>
  30. #include <glad/glad.h>
  31. #include <stdexcept>
  32. #include <utility>
  33. #include <thread>
  34. #include <stb/stb_image_write.h>
  35. #include <iostream>
  36. #include <iomanip>
  37. application::application():
  38. closed(false),
  39. exit_status(EXIT_SUCCESS),
  40. current_state{std::string(), nullptr, nullptr},
  41. queued_state{std::string(), nullptr, nullptr},
  42. update_callback(nullptr),
  43. render_callback(nullptr),
  44. fullscreen(true),
  45. v_sync(false),
  46. cursor_visible(true),
  47. display_dimensions({0, 0}),
  48. display_dpi(0.0f),
  49. window_dimensions({0, 0}),
  50. viewport_dimensions({0, 0}),
  51. mouse_position({0, 0}),
  52. update_rate(60.0),
  53. logger(nullptr),
  54. sdl_window(nullptr),
  55. sdl_gl_context(nullptr)
  56. {
  57. // Setup logging
  58. logger = new debug::logger();
  59. // Get SDL compiled version
  60. SDL_version sdl_compiled_version;
  61. SDL_VERSION(&sdl_compiled_version);
  62. 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);
  63. logger->log("Compiled against SDL " + sdl_compiled_version_string);
  64. // Get SDL linked version
  65. SDL_version sdl_linked_version;
  66. SDL_GetVersion(&sdl_linked_version);
  67. 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);
  68. logger->log("Linking against SDL " + sdl_linked_version_string);
  69. // Init SDL
  70. logger->push_task("Initializing SDL");
  71. if (SDL_Init(SDL_INIT_VIDEO) != 0)
  72. {
  73. logger->pop_task(EXIT_FAILURE);
  74. throw std::runtime_error("Failed to initialize SDL");
  75. }
  76. else
  77. {
  78. logger->pop_task(EXIT_SUCCESS);
  79. }
  80. // Load default OpenGL library
  81. logger->push_task("Loading OpenGL library");
  82. if (SDL_GL_LoadLibrary(nullptr) != 0)
  83. {
  84. logger->pop_task(EXIT_FAILURE);
  85. }
  86. else
  87. {
  88. logger->pop_task(EXIT_SUCCESS);
  89. }
  90. // Set window creation hints
  91. SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  92. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  93. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
  94. SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
  95. SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  96. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  97. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  98. // Get display dimensions
  99. SDL_DisplayMode sdl_desktop_display_mode;
  100. if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0)
  101. {
  102. logger->error("Failed to detect desktop display mode: \"" + std::string(SDL_GetError()) + "\"");
  103. throw std::runtime_error("Failed to detect desktop display mode");
  104. }
  105. else
  106. {
  107. display_dimensions = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
  108. }
  109. // Get display DPI
  110. if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0)
  111. {
  112. logger->error("Failed to detect display DPI: \"" + std::string(SDL_GetError()) + "\"");
  113. throw std::runtime_error("Failed to detect display DPI");
  114. }
  115. else
  116. {
  117. 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");
  118. }
  119. // Create a hidden fullscreen window
  120. logger->push_task("Creating " + std::to_string(display_dimensions[0]) + "x" + std::to_string(display_dimensions[1]) + " window");
  121. sdl_window = SDL_CreateWindow
  122. (
  123. "Antkeeper",
  124. SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
  125. display_dimensions[0], display_dimensions[1],
  126. SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN
  127. );
  128. if (!sdl_window)
  129. {
  130. logger->pop_task(EXIT_FAILURE);
  131. throw std::runtime_error("Failed to create SDL window");
  132. }
  133. else
  134. {
  135. logger->pop_task(EXIT_SUCCESS);
  136. }
  137. // Create OpenGL context
  138. logger->push_task("Creating OpenGL 3.3 context");
  139. sdl_gl_context = SDL_GL_CreateContext(sdl_window);
  140. if (!sdl_gl_context)
  141. {
  142. logger->pop_task(EXIT_FAILURE);
  143. throw std::runtime_error("Failed to create OpenGL context");
  144. }
  145. else
  146. {
  147. logger->pop_task(EXIT_SUCCESS);
  148. }
  149. // Make OpenGL context current
  150. logger->push_task("Making OpenGL context current");
  151. if (SDL_GL_MakeCurrent(sdl_window, sdl_gl_context) != 0)
  152. {
  153. logger->pop_task(EXIT_FAILURE);
  154. throw std::runtime_error("Failed to make OpenGL context current");
  155. }
  156. else
  157. {
  158. logger->pop_task(EXIT_SUCCESS);
  159. }
  160. // Load OpenGL functions via GLAD
  161. logger->push_task("Loading OpenGL functions");
  162. if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
  163. {
  164. logger->pop_task(EXIT_FAILURE);
  165. throw std::runtime_error("Failed to load OpenGL functions");
  166. }
  167. else
  168. {
  169. logger->pop_task(EXIT_SUCCESS);
  170. }
  171. // Update window size and viewport size
  172. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  173. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  174. // Set v-sync mode
  175. set_v_sync(true);
  176. // Init SDL joystick and gamepad subsystems
  177. logger->push_task("Initializing SDL Joystick and Game Controller subsystems");
  178. if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
  179. {
  180. logger->pop_task(EXIT_FAILURE);
  181. }
  182. else
  183. {
  184. logger->pop_task(EXIT_SUCCESS);
  185. }
  186. // Setup rasterizer
  187. rasterizer = new gl::rasterizer();
  188. // Setup events
  189. event_dispatcher = new ::event_dispatcher();
  190. // Setup input
  191. keyboard = new input::keyboard();
  192. keyboard->set_event_dispatcher(event_dispatcher);
  193. mouse = new input::mouse();
  194. mouse->set_event_dispatcher(event_dispatcher);
  195. // Connect gamepads
  196. translate_sdl_events();
  197. // Setup frame scheduler
  198. frame_scheduler = new ::frame_scheduler();
  199. frame_scheduler->set_update_callback(std::bind(&application::update, this, std::placeholders::_1, std::placeholders::_2));
  200. frame_scheduler->set_render_callback(std::bind(&application::render, this, std::placeholders::_1));
  201. frame_scheduler->set_update_rate(update_rate);
  202. frame_scheduler->set_max_frame_duration(0.25);
  203. // Setup performance sampling
  204. performance_sampler = new debug::performance_sampler();
  205. performance_sampler->set_sample_size(15);
  206. }
  207. application::~application()
  208. {
  209. // Destroy the OpenGL context
  210. SDL_GL_DeleteContext(sdl_gl_context);
  211. // Destroy the SDL window
  212. SDL_DestroyWindow(sdl_window);
  213. // Shutdown SDL
  214. SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
  215. SDL_Quit();
  216. }
  217. void application::close(int status)
  218. {
  219. closed = true;
  220. exit_status = status;
  221. }
  222. int application::execute(const application::state& initial_state)
  223. {
  224. try
  225. {
  226. // Enter initial application state
  227. change_state(initial_state);
  228. // Perform initial update
  229. update(0.0, 0.0);
  230. // Reset frame scheduler
  231. frame_scheduler->reset();
  232. // Schedule frames until closed
  233. while (!closed)
  234. {
  235. translate_sdl_events();
  236. // Enter queued state (if any)
  237. if (queued_state.enter != nullptr || queued_state.exit != nullptr)
  238. {
  239. // Make a copy of the queued state
  240. application::state queued_state_copy = queued_state;
  241. // Clear the queued state
  242. queued_state = {std::string(), nullptr, nullptr};
  243. // Enter the queued state
  244. change_state(queued_state_copy);
  245. }
  246. // Tick frame scheduler
  247. frame_scheduler->tick();
  248. // Sample frame duration
  249. performance_sampler->sample(frame_scheduler->get_frame_duration());
  250. }
  251. // Exit current state
  252. change_state({std::string(), nullptr, nullptr});
  253. }
  254. catch (const std::exception& e)
  255. {
  256. // Print exception to logger
  257. logger->error(std::string("Unhandled exception: \"") + e.what() + std::string("\""));
  258. // Show error message box with unhandled exception
  259. SDL_ShowSimpleMessageBox
  260. (
  261. SDL_MESSAGEBOX_ERROR,
  262. "Unhandled Exception",
  263. e.what(),
  264. sdl_window
  265. );
  266. // Set exit status to failure
  267. exit_status = EXIT_FAILURE;
  268. }
  269. return exit_status;
  270. }
  271. void application::change_state(const application::state& next_state)
  272. {
  273. // Exit current state
  274. if (current_state.exit)
  275. {
  276. logger->push_task("Exiting application state \"" + current_state.name + "\"");
  277. try
  278. {
  279. current_state.exit();
  280. }
  281. catch (...)
  282. {
  283. logger->pop_task(EXIT_FAILURE);
  284. throw;
  285. }
  286. logger->pop_task(EXIT_SUCCESS);
  287. }
  288. current_state = next_state;
  289. // Enter next state
  290. if (current_state.enter)
  291. {
  292. logger->push_task("Entering application state \"" + current_state.name + "\"");
  293. try
  294. {
  295. current_state.enter();
  296. }
  297. catch (...)
  298. {
  299. logger->pop_task(EXIT_FAILURE);
  300. throw;
  301. }
  302. logger->pop_task(EXIT_SUCCESS);
  303. }
  304. // Enter queued state (if any)
  305. if (queued_state.enter != nullptr || queued_state.exit != nullptr)
  306. {
  307. // Make a copy of the queued state
  308. application::state queued_state_copy = queued_state;
  309. // Clear the queued state
  310. queued_state = {std::string(), nullptr, nullptr};
  311. // Enter the queued state
  312. change_state(queued_state_copy);
  313. }
  314. }
  315. void application::queue_state(const application::state& next_state)
  316. {
  317. queued_state = next_state;
  318. logger->log("Queued application state \"" + queued_state.name + "\"");
  319. }
  320. std::shared_ptr<image> application::capture_frame() const
  321. {
  322. int w = viewport_dimensions[0];
  323. int h = viewport_dimensions[1];
  324. std::shared_ptr<image> frame = std::make_shared<image>();
  325. frame->format(1, 3);
  326. frame->resize(w, h);
  327. logger->log("starting read");
  328. // Read pixel data from framebuffer into image
  329. glReadBuffer(GL_BACK);
  330. logger->log("buffer read");
  331. glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, frame->get_pixels());
  332. logger->log("ending read");
  333. return std::move(frame);
  334. }
  335. void application::save_frame(const std::string& path) const
  336. {
  337. logger->push_task("Saving screenshot to \"" + path + "\"");
  338. auto frame = capture_frame();
  339. std::thread
  340. (
  341. [frame, path]
  342. {
  343. stbi_flip_vertically_on_write(1);
  344. stbi_write_png(path.c_str(), frame->get_width(), frame->get_height(), frame->get_channel_count(), frame->get_pixels(), frame->get_width() * frame->get_channel_count());
  345. }
  346. ).detach();
  347. logger->pop_task(EXIT_SUCCESS);
  348. }
  349. void application::set_update_callback(const update_callback_type& callback)
  350. {
  351. update_callback = callback;
  352. }
  353. void application::set_render_callback(const render_callback_type& callback)
  354. {
  355. render_callback = callback;
  356. }
  357. void application::set_update_rate(double frequency)
  358. {
  359. update_rate = frequency;
  360. frame_scheduler->set_update_rate(update_rate);
  361. }
  362. void application::set_title(const std::string& title)
  363. {
  364. SDL_SetWindowTitle(sdl_window, title.c_str());
  365. }
  366. void application::set_cursor_visible(bool visible)
  367. {
  368. SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE);
  369. cursor_visible = visible;
  370. }
  371. void application::set_relative_mouse_mode(bool enabled)
  372. {
  373. if (enabled)
  374. {
  375. SDL_GetMouseState(&mouse_position[0], &mouse_position[1]);
  376. SDL_ShowCursor(SDL_DISABLE);
  377. SDL_SetRelativeMouseMode(SDL_TRUE);
  378. }
  379. else
  380. {
  381. SDL_SetRelativeMouseMode(SDL_FALSE);
  382. SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]);
  383. if (cursor_visible)
  384. SDL_ShowCursor(SDL_ENABLE);
  385. }
  386. }
  387. void application::resize_window(int width, int height)
  388. {
  389. int x = (display_dimensions[0] >> 1) - (width >> 1);
  390. int y = (display_dimensions[1] >> 1) - (height >> 1);
  391. // Resize and center window
  392. SDL_SetWindowPosition(sdl_window, x, y);
  393. SDL_SetWindowSize(sdl_window, width, height);
  394. window_resized();
  395. }
  396. void application::set_fullscreen(bool fullscreen)
  397. {
  398. if (this->fullscreen != fullscreen)
  399. {
  400. this->fullscreen = fullscreen;
  401. if (fullscreen)
  402. {
  403. SDL_HideWindow(sdl_window);
  404. SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
  405. SDL_ShowWindow(sdl_window);
  406. }
  407. else
  408. {
  409. SDL_SetWindowFullscreen(sdl_window, 0);
  410. SDL_SetWindowBordered(sdl_window, SDL_TRUE);
  411. SDL_SetWindowResizable(sdl_window, SDL_TRUE);
  412. }
  413. }
  414. }
  415. void application::set_v_sync(bool v_sync)
  416. {
  417. if (this->v_sync != v_sync)
  418. {
  419. if (v_sync)
  420. {
  421. logger->push_task("Enabling adaptive v-sync");
  422. if (SDL_GL_SetSwapInterval(-1) != 0)
  423. {
  424. logger->pop_task(EXIT_FAILURE);
  425. logger->push_task("Enabling synchronized v-sync");
  426. if (SDL_GL_SetSwapInterval(-1) != 0)
  427. {
  428. logger->pop_task(EXIT_FAILURE);
  429. }
  430. else
  431. {
  432. this->v_sync = v_sync;
  433. logger->pop_task(EXIT_SUCCESS);
  434. }
  435. }
  436. else
  437. {
  438. this->v_sync = v_sync;
  439. logger->pop_task(EXIT_SUCCESS);
  440. }
  441. }
  442. else
  443. {
  444. logger->push_task("Disabling v-sync");
  445. if (SDL_GL_SetSwapInterval(0) != 0)
  446. {
  447. logger->pop_task(EXIT_FAILURE);
  448. }
  449. else
  450. {
  451. this->v_sync = v_sync;
  452. logger->pop_task(EXIT_SUCCESS);
  453. }
  454. }
  455. }
  456. }
  457. void application::set_window_opacity(float opacity)
  458. {
  459. SDL_SetWindowOpacity(sdl_window, opacity);
  460. }
  461. void application::swap_buffers()
  462. {
  463. SDL_GL_SwapWindow(sdl_window);
  464. }
  465. void application::show_window()
  466. {
  467. SDL_ShowWindow(sdl_window);
  468. //SDL_GL_MakeCurrent(sdl_window, sdl_gl_context);
  469. }
  470. void application::hide_window()
  471. {
  472. SDL_HideWindow(sdl_window);
  473. }
  474. void application::add_game_controller_mappings(const void* mappings, std::size_t size)
  475. {
  476. logger->push_task("Adding SDL game controller mappings");
  477. int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, size), 0);
  478. if (mapping_count == -1)
  479. {
  480. logger->pop_task(EXIT_FAILURE);
  481. }
  482. else
  483. {
  484. logger->log("Added " + std::to_string(mapping_count) + " SDL game controller mappings");
  485. logger->pop_task(EXIT_SUCCESS);
  486. }
  487. }
  488. void application::update(double t, double dt)
  489. {
  490. translate_sdl_events();
  491. event_dispatcher->update(t);
  492. if (update_callback)
  493. {
  494. update_callback(t, dt);
  495. }
  496. /*
  497. static int frame = 0;
  498. if (frame % 60 == 0)
  499. {
  500. std::cout << std::fixed;
  501. std::cout << std::setprecision(2);
  502. std::cout << performance_sampler->mean_frame_duration() * 1000.0 << "\n";
  503. }
  504. ++frame;
  505. */
  506. }
  507. void application::render(double alpha)
  508. {
  509. /*
  510. std::cout << std::fixed;
  511. std::cout << std::setprecision(2);
  512. std::cout << performance_sampler->mean_frame_duration() * 1000.0 << std::endl;
  513. */
  514. if (render_callback)
  515. {
  516. render_callback(alpha);
  517. }
  518. SDL_GL_SwapWindow(sdl_window);
  519. }
  520. void application::translate_sdl_events()
  521. {
  522. // Mouse motion event accumulators
  523. bool mouse_motion = false;
  524. int mouse_x;
  525. int mouse_y;
  526. int mouse_dx = 0;
  527. int mouse_dy = 0;
  528. SDL_Event sdl_event;
  529. while (SDL_PollEvent(&sdl_event))
  530. {
  531. switch (sdl_event.type)
  532. {
  533. case SDL_KEYDOWN:
  534. case SDL_KEYUP:
  535. {
  536. if (sdl_event.key.repeat == 0)
  537. {
  538. input::scancode scancode = input::scancode::unknown;
  539. if (sdl_event.key.keysym.scancode <= SDL_SCANCODE_APP2)
  540. {
  541. scancode = input::sdl_scancode_table[sdl_event.key.keysym.scancode];
  542. }
  543. if (sdl_event.type == SDL_KEYDOWN)
  544. keyboard->press(scancode);
  545. else
  546. keyboard->release(scancode);
  547. }
  548. break;
  549. }
  550. case SDL_MOUSEMOTION:
  551. {
  552. // More than one mouse motion event is often generated per frame, and may be a source of lag.
  553. // Mouse events are accumulated here to prevent excess function calls and allocations
  554. mouse_motion = true;
  555. mouse_x = sdl_event.motion.x;
  556. mouse_y = sdl_event.motion.y;
  557. mouse_dx += sdl_event.motion.xrel;
  558. mouse_dy += sdl_event.motion.yrel;
  559. break;
  560. }
  561. case SDL_MOUSEBUTTONDOWN:
  562. {
  563. mouse->press(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  564. break;
  565. }
  566. case SDL_MOUSEBUTTONUP:
  567. {
  568. mouse->release(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  569. break;
  570. }
  571. case SDL_MOUSEWHEEL:
  572. {
  573. int direction = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1;
  574. mouse->scroll(sdl_event.wheel.x * direction, sdl_event.wheel.y * direction);
  575. break;
  576. }
  577. case SDL_CONTROLLERBUTTONDOWN:
  578. {
  579. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  580. {
  581. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  582. {
  583. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  584. it->second->press(button);
  585. }
  586. }
  587. break;
  588. }
  589. case SDL_CONTROLLERBUTTONUP:
  590. {
  591. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  592. {
  593. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  594. {
  595. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  596. it->second->release(button);
  597. }
  598. }
  599. break;
  600. }
  601. case SDL_CONTROLLERAXISMOTION:
  602. {
  603. if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
  604. {
  605. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  606. {
  607. input::gamepad_axis axis = input::sdl_axis_table[sdl_event.caxis.axis];
  608. float value = sdl_event.caxis.value;
  609. static const float min = static_cast<float>(std::numeric_limits<std::int16_t>::min());
  610. static const float max = static_cast<float>(std::numeric_limits<std::int16_t>::max());
  611. value /= (value < 0.0f) ? -min : max;
  612. it->second->move(axis, value);
  613. }
  614. }
  615. break;
  616. }
  617. case SDL_CONTROLLERDEVICEADDED:
  618. {
  619. if (SDL_IsGameController(sdl_event.cdevice.which))
  620. {
  621. SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
  622. std::string controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  623. if (sdl_controller)
  624. {
  625. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  626. {
  627. logger->log("Reconnected gamepad \"" + controller_name + "\"");
  628. it->second->connect(true);
  629. }
  630. else
  631. {
  632. // Get gamepad GUID
  633. SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
  634. SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
  635. char guid_buffer[33];
  636. std::memset(guid_buffer, '\0', 33);
  637. SDL_JoystickGetGUIDString(sdl_guid, &guid_buffer[0], 33);
  638. std::string guid(guid_buffer);
  639. logger->log("Connected gamepad \"" + controller_name + "\" with GUID: " + guid + "");
  640. // Create new gamepad
  641. input::gamepad* gamepad = new input::gamepad();
  642. gamepad->set_event_dispatcher(event_dispatcher);
  643. gamepad->set_guid(guid);
  644. // Add gamepad to list and map
  645. gamepads.push_back(gamepad);
  646. gamepad_map[sdl_event.cdevice.which] = gamepad;
  647. // Send controller connected event
  648. gamepad->connect(false);
  649. }
  650. }
  651. else
  652. {
  653. logger->error("Failed to connected gamepad \"" + controller_name + "\"");
  654. }
  655. }
  656. break;
  657. }
  658. case SDL_CONTROLLERDEVICEREMOVED:
  659. {
  660. SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
  661. if (sdl_controller)
  662. {
  663. SDL_GameControllerClose(sdl_controller);
  664. logger->log("Disconnected gamepad");
  665. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  666. {
  667. it->second->disconnect();
  668. }
  669. }
  670. break;
  671. }
  672. case SDL_WINDOWEVENT:
  673. {
  674. if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED)
  675. {
  676. window_resized();
  677. }
  678. break;
  679. }
  680. case SDL_QUIT:
  681. {
  682. close(EXIT_SUCCESS);
  683. break;
  684. }
  685. }
  686. }
  687. // Process accumulated mouse motion events
  688. if (mouse_motion)
  689. {
  690. mouse->move(mouse_x, mouse_y, mouse_dx, mouse_dy);
  691. }
  692. }
  693. void application::window_resized()
  694. {
  695. // Update window size and viewport size
  696. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  697. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  698. rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
  699. window_resized_event event;
  700. event.w = window_dimensions[0];
  701. event.h = window_dimensions[1];
  702. event_dispatcher->queue(event);
  703. }