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

790 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(true),
  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. int swap_interval = (v_sync) ? 1 : 0;
  176. logger->push_task((swap_interval) ? "Enabling v-sync" : "Disabling v-sync");
  177. if (SDL_GL_SetSwapInterval(swap_interval) != 0)
  178. {
  179. logger->pop_task(EXIT_FAILURE);
  180. }
  181. else
  182. {
  183. logger->pop_task(EXIT_SUCCESS);
  184. }
  185. // Init SDL joystick and gamepad subsystems
  186. logger->push_task("Initializing SDL Joystick and Game Controller subsystems");
  187. if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
  188. {
  189. logger->pop_task(EXIT_FAILURE);
  190. }
  191. else
  192. {
  193. logger->pop_task(EXIT_SUCCESS);
  194. }
  195. // Setup rasterizer
  196. rasterizer = new gl::rasterizer();
  197. // Setup events
  198. event_dispatcher = new ::event_dispatcher();
  199. // Setup input
  200. keyboard = new input::keyboard();
  201. keyboard->set_event_dispatcher(event_dispatcher);
  202. mouse = new input::mouse();
  203. mouse->set_event_dispatcher(event_dispatcher);
  204. // Connect gamepads
  205. translate_sdl_events();
  206. // Setup frame scheduler
  207. frame_scheduler = new ::frame_scheduler();
  208. frame_scheduler->set_update_callback(std::bind(&application::update, this, std::placeholders::_1, std::placeholders::_2));
  209. frame_scheduler->set_render_callback(std::bind(&application::render, this, std::placeholders::_1));
  210. frame_scheduler->set_update_rate(update_rate);
  211. frame_scheduler->set_max_frame_duration(0.25);
  212. // Setup performance sampling
  213. performance_sampler = new debug::performance_sampler();
  214. performance_sampler->set_sample_size(15);
  215. }
  216. application::~application()
  217. {
  218. // Destroy the OpenGL context
  219. SDL_GL_DeleteContext(sdl_gl_context);
  220. // Destroy the SDL window
  221. SDL_DestroyWindow(sdl_window);
  222. // Shutdown SDL
  223. SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
  224. SDL_Quit();
  225. }
  226. void application::close(int status)
  227. {
  228. closed = true;
  229. exit_status = status;
  230. }
  231. int application::execute(const application::state& initial_state)
  232. {
  233. try
  234. {
  235. // Enter initial application state
  236. change_state(initial_state);
  237. // Perform initial update
  238. update(0.0, 0.0);
  239. // Reset frame scheduler
  240. frame_scheduler->reset();
  241. // Schedule frames until closed
  242. while (!closed)
  243. {
  244. translate_sdl_events();
  245. // Enter queued state (if any)
  246. if (queued_state.enter != nullptr || queued_state.exit != nullptr)
  247. {
  248. // Make a copy of the queued state
  249. application::state queued_state_copy = queued_state;
  250. // Clear the queued state
  251. queued_state = {std::string(), nullptr, nullptr};
  252. // Enter the queued state
  253. change_state(queued_state_copy);
  254. }
  255. // Tick frame scheduler
  256. frame_scheduler->tick();
  257. // Sample frame duration
  258. performance_sampler->sample(frame_scheduler->get_frame_duration());
  259. }
  260. // Exit current state
  261. change_state({std::string(), nullptr, nullptr});
  262. }
  263. catch (const std::exception& e)
  264. {
  265. // Print exception to logger
  266. logger->error(std::string("Unhandled exception: \"") + e.what() + std::string("\""));
  267. // Show error message box with unhandled exception
  268. SDL_ShowSimpleMessageBox
  269. (
  270. SDL_MESSAGEBOX_ERROR,
  271. "Unhandled Exception",
  272. e.what(),
  273. sdl_window
  274. );
  275. // Set exit status to failure
  276. exit_status = EXIT_FAILURE;
  277. }
  278. return exit_status;
  279. }
  280. void application::change_state(const application::state& next_state)
  281. {
  282. // Exit current state
  283. if (current_state.exit)
  284. {
  285. logger->push_task("Exiting application state \"" + current_state.name + "\"");
  286. try
  287. {
  288. current_state.exit();
  289. }
  290. catch (...)
  291. {
  292. logger->pop_task(EXIT_FAILURE);
  293. throw;
  294. }
  295. logger->pop_task(EXIT_SUCCESS);
  296. }
  297. current_state = next_state;
  298. // Enter next state
  299. if (current_state.enter)
  300. {
  301. logger->push_task("Entering application state \"" + current_state.name + "\"");
  302. try
  303. {
  304. current_state.enter();
  305. }
  306. catch (...)
  307. {
  308. logger->pop_task(EXIT_FAILURE);
  309. throw;
  310. }
  311. logger->pop_task(EXIT_SUCCESS);
  312. }
  313. // Enter queued state (if any)
  314. if (queued_state.enter != nullptr || queued_state.exit != nullptr)
  315. {
  316. // Make a copy of the queued state
  317. application::state queued_state_copy = queued_state;
  318. // Clear the queued state
  319. queued_state = {std::string(), nullptr, nullptr};
  320. // Enter the queued state
  321. change_state(queued_state_copy);
  322. }
  323. }
  324. void application::queue_state(const application::state& next_state)
  325. {
  326. queued_state = next_state;
  327. logger->log("Queued application state \"" + queued_state.name + "\"");
  328. }
  329. std::shared_ptr<image> application::capture_frame() const
  330. {
  331. int w = viewport_dimensions[0];
  332. int h = viewport_dimensions[1];
  333. std::shared_ptr<image> frame = std::make_shared<image>();
  334. frame->format(1, 3);
  335. frame->resize(w, h);
  336. logger->log("starting read");
  337. // Read pixel data from framebuffer into image
  338. glReadBuffer(GL_BACK);
  339. logger->log("buffer read");
  340. glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, frame->get_pixels());
  341. logger->log("ending read");
  342. return std::move(frame);
  343. }
  344. void application::save_frame(const std::string& path) const
  345. {
  346. logger->push_task("Saving screenshot to \"" + path + "\"");
  347. auto frame = capture_frame();
  348. std::thread
  349. (
  350. [frame, path]
  351. {
  352. stbi_flip_vertically_on_write(1);
  353. 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());
  354. }
  355. ).detach();
  356. logger->pop_task(EXIT_SUCCESS);
  357. }
  358. void application::set_update_callback(const update_callback_type& callback)
  359. {
  360. update_callback = callback;
  361. }
  362. void application::set_render_callback(const render_callback_type& callback)
  363. {
  364. render_callback = callback;
  365. }
  366. void application::set_update_rate(double frequency)
  367. {
  368. update_rate = frequency;
  369. frame_scheduler->set_update_rate(update_rate);
  370. }
  371. void application::set_title(const std::string& title)
  372. {
  373. SDL_SetWindowTitle(sdl_window, title.c_str());
  374. }
  375. void application::set_cursor_visible(bool visible)
  376. {
  377. SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE);
  378. cursor_visible = visible;
  379. }
  380. void application::set_relative_mouse_mode(bool enabled)
  381. {
  382. if (enabled)
  383. {
  384. SDL_GetMouseState(&mouse_position[0], &mouse_position[1]);
  385. SDL_ShowCursor(SDL_DISABLE);
  386. SDL_SetRelativeMouseMode(SDL_TRUE);
  387. }
  388. else
  389. {
  390. SDL_SetRelativeMouseMode(SDL_FALSE);
  391. SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]);
  392. if (cursor_visible)
  393. SDL_ShowCursor(SDL_ENABLE);
  394. }
  395. }
  396. void application::resize_window(int width, int height)
  397. {
  398. int x = (display_dimensions[0] >> 1) - (width >> 1);
  399. int y = (display_dimensions[1] >> 1) - (height >> 1);
  400. // Resize and center window
  401. SDL_SetWindowPosition(sdl_window, x, y);
  402. SDL_SetWindowSize(sdl_window, width, height);
  403. window_resized();
  404. }
  405. void application::set_fullscreen(bool fullscreen)
  406. {
  407. if (this->fullscreen != fullscreen)
  408. {
  409. this->fullscreen = fullscreen;
  410. if (fullscreen)
  411. {
  412. SDL_HideWindow(sdl_window);
  413. SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
  414. SDL_ShowWindow(sdl_window);
  415. }
  416. else
  417. {
  418. SDL_SetWindowFullscreen(sdl_window, 0);
  419. SDL_SetWindowBordered(sdl_window, SDL_TRUE);
  420. SDL_SetWindowResizable(sdl_window, SDL_TRUE);
  421. }
  422. }
  423. }
  424. void application::set_v_sync(bool v_sync)
  425. {
  426. if (this->v_sync != v_sync)
  427. {
  428. this->v_sync = v_sync;
  429. SDL_GL_SetSwapInterval((v_sync) ? 1 : 0);
  430. }
  431. }
  432. void application::set_window_opacity(float opacity)
  433. {
  434. SDL_SetWindowOpacity(sdl_window, opacity);
  435. }
  436. void application::swap_buffers()
  437. {
  438. SDL_GL_SwapWindow(sdl_window);
  439. }
  440. void application::show_window()
  441. {
  442. SDL_ShowWindow(sdl_window);
  443. //SDL_GL_MakeCurrent(sdl_window, sdl_gl_context);
  444. }
  445. void application::hide_window()
  446. {
  447. SDL_HideWindow(sdl_window);
  448. }
  449. void application::add_game_controller_mappings(const void* mappings, std::size_t size)
  450. {
  451. logger->push_task("Adding SDL game controller mappings");
  452. int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, size), 0);
  453. if (mapping_count == -1)
  454. {
  455. logger->pop_task(EXIT_FAILURE);
  456. }
  457. else
  458. {
  459. logger->log("Added " + std::to_string(mapping_count) + " SDL game controller mappings");
  460. logger->pop_task(EXIT_SUCCESS);
  461. }
  462. }
  463. void application::update(double t, double dt)
  464. {
  465. translate_sdl_events();
  466. event_dispatcher->update(t);
  467. if (update_callback)
  468. {
  469. update_callback(t, dt);
  470. }
  471. /*
  472. static int frame = 0;
  473. if (frame % 60 == 0)
  474. {
  475. std::cout << std::fixed;
  476. std::cout << std::setprecision(2);
  477. std::cout << performance_sampler->mean_frame_duration() * 1000.0 << "\n";
  478. }
  479. ++frame;
  480. */
  481. }
  482. void application::render(double alpha)
  483. {
  484. /*
  485. std::cout << std::fixed;
  486. std::cout << std::setprecision(2);
  487. std::cout << performance_sampler->mean_frame_duration() * 1000.0 << std::endl;
  488. */
  489. if (render_callback)
  490. {
  491. render_callback(alpha);
  492. }
  493. SDL_GL_SwapWindow(sdl_window);
  494. }
  495. void application::translate_sdl_events()
  496. {
  497. // Mouse motion event accumulators
  498. bool mouse_motion = false;
  499. int mouse_x;
  500. int mouse_y;
  501. int mouse_dx = 0;
  502. int mouse_dy = 0;
  503. SDL_Event sdl_event;
  504. while (SDL_PollEvent(&sdl_event))
  505. {
  506. switch (sdl_event.type)
  507. {
  508. case SDL_KEYDOWN:
  509. case SDL_KEYUP:
  510. {
  511. if (sdl_event.key.repeat == 0)
  512. {
  513. input::scancode scancode = input::scancode::unknown;
  514. if (sdl_event.key.keysym.scancode <= SDL_SCANCODE_APP2)
  515. {
  516. scancode = input::sdl_scancode_table[sdl_event.key.keysym.scancode];
  517. }
  518. if (sdl_event.type == SDL_KEYDOWN)
  519. keyboard->press(scancode);
  520. else
  521. keyboard->release(scancode);
  522. }
  523. break;
  524. }
  525. case SDL_MOUSEMOTION:
  526. {
  527. // More than one mouse motion event is often generated per frame, and may be a source of lag.
  528. // Mouse events are accumulated here to prevent excess function calls and allocations
  529. mouse_motion = true;
  530. mouse_x = sdl_event.motion.x;
  531. mouse_y = sdl_event.motion.y;
  532. mouse_dx += sdl_event.motion.xrel;
  533. mouse_dy += sdl_event.motion.yrel;
  534. break;
  535. }
  536. case SDL_MOUSEBUTTONDOWN:
  537. {
  538. mouse->press(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  539. break;
  540. }
  541. case SDL_MOUSEBUTTONUP:
  542. {
  543. mouse->release(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  544. break;
  545. }
  546. case SDL_MOUSEWHEEL:
  547. {
  548. int direction = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1;
  549. mouse->scroll(sdl_event.wheel.x * direction, sdl_event.wheel.y * direction);
  550. break;
  551. }
  552. case SDL_CONTROLLERBUTTONDOWN:
  553. {
  554. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  555. {
  556. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  557. {
  558. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  559. it->second->press(button);
  560. }
  561. }
  562. break;
  563. }
  564. case SDL_CONTROLLERBUTTONUP:
  565. {
  566. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  567. {
  568. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  569. {
  570. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  571. it->second->release(button);
  572. }
  573. }
  574. break;
  575. }
  576. case SDL_CONTROLLERAXISMOTION:
  577. {
  578. if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
  579. {
  580. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  581. {
  582. input::gamepad_axis axis = input::sdl_axis_table[sdl_event.caxis.axis];
  583. float value = sdl_event.caxis.value;
  584. static const float min = static_cast<float>(std::numeric_limits<std::int16_t>::min());
  585. static const float max = static_cast<float>(std::numeric_limits<std::int16_t>::max());
  586. value /= (value < 0.0f) ? -min : max;
  587. it->second->move(axis, value);
  588. }
  589. }
  590. break;
  591. }
  592. case SDL_CONTROLLERDEVICEADDED:
  593. {
  594. if (SDL_IsGameController(sdl_event.cdevice.which))
  595. {
  596. SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
  597. std::string controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  598. if (sdl_controller)
  599. {
  600. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  601. {
  602. logger->log("Reconnected gamepad \"" + controller_name + "\"");
  603. it->second->connect(true);
  604. }
  605. else
  606. {
  607. // Get gamepad GUID
  608. SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
  609. SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
  610. char guid_buffer[33];
  611. std::memset(guid_buffer, '\0', 33);
  612. SDL_JoystickGetGUIDString(sdl_guid, &guid_buffer[0], 33);
  613. std::string guid(guid_buffer);
  614. logger->log("Connected gamepad \"" + controller_name + "\" with GUID: " + guid + "");
  615. // Create new gamepad
  616. input::gamepad* gamepad = new input::gamepad();
  617. gamepad->set_event_dispatcher(event_dispatcher);
  618. gamepad->set_guid(guid);
  619. // Add gamepad to list and map
  620. gamepads.push_back(gamepad);
  621. gamepad_map[sdl_event.cdevice.which] = gamepad;
  622. // Send controller connected event
  623. gamepad->connect(false);
  624. }
  625. }
  626. else
  627. {
  628. logger->error("Failed to connected gamepad \"" + controller_name + "\"");
  629. }
  630. }
  631. break;
  632. }
  633. case SDL_CONTROLLERDEVICEREMOVED:
  634. {
  635. SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
  636. if (sdl_controller)
  637. {
  638. SDL_GameControllerClose(sdl_controller);
  639. logger->log("Disconnected gamepad");
  640. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  641. {
  642. it->second->disconnect();
  643. }
  644. }
  645. break;
  646. }
  647. case SDL_WINDOWEVENT:
  648. {
  649. if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED)
  650. {
  651. window_resized();
  652. }
  653. break;
  654. }
  655. case SDL_QUIT:
  656. {
  657. close(EXIT_SUCCESS);
  658. break;
  659. }
  660. }
  661. }
  662. // Process accumulated mouse motion events
  663. if (mouse_motion)
  664. {
  665. mouse->move(mouse_x, mouse_y, mouse_dx, mouse_dy);
  666. }
  667. }
  668. void application::window_resized()
  669. {
  670. // Update window size and viewport size
  671. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  672. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  673. rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
  674. window_resized_event event;
  675. event.w = window_dimensions[0];
  676. event.h = window_dimensions[1];
  677. event_dispatcher->queue(event);
  678. }