💿🐜 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. vsync(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. "",
  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. // Update window size and viewport size
  150. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  151. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  152. // Make OpenGL context current
  153. logger->push_task("Making OpenGL context current");
  154. if (SDL_GL_MakeCurrent(sdl_window, sdl_gl_context) != 0)
  155. {
  156. logger->pop_task(EXIT_FAILURE);
  157. throw std::runtime_error("Failed to make OpenGL context current");
  158. }
  159. else
  160. {
  161. logger->pop_task(EXIT_SUCCESS);
  162. }
  163. // Load OpenGL functions via GLAD
  164. logger->push_task("Loading OpenGL functions");
  165. if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
  166. {
  167. logger->pop_task(EXIT_FAILURE);
  168. throw std::runtime_error("Failed to load OpenGL functions");
  169. }
  170. else
  171. {
  172. logger->pop_task(EXIT_SUCCESS);
  173. }
  174. // Set v-sync mode
  175. int swap_interval = (vsync) ? 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_vsync(bool vsync)
  425. {
  426. if (this->vsync != vsync)
  427. {
  428. this->vsync = vsync;
  429. SDL_GL_SetSwapInterval((vsync) ? 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. }
  444. void application::hide_window()
  445. {
  446. SDL_HideWindow(sdl_window);
  447. }
  448. void application::add_game_controller_mappings(const void* mappings, std::size_t size)
  449. {
  450. logger->push_task("Adding SDL game controller mappings");
  451. int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, size), 0);
  452. if (mapping_count == -1)
  453. {
  454. logger->pop_task(EXIT_FAILURE);
  455. }
  456. else
  457. {
  458. logger->log("Added " + std::to_string(mapping_count) + " SDL game controller mappings");
  459. logger->pop_task(EXIT_SUCCESS);
  460. }
  461. }
  462. void application::update(double t, double dt)
  463. {
  464. translate_sdl_events();
  465. event_dispatcher->update(t);
  466. if (update_callback)
  467. {
  468. update_callback(t, dt);
  469. }
  470. /*
  471. static int frame = 0;
  472. if (frame % 60 == 0)
  473. {
  474. std::cout << std::fixed;
  475. std::cout << std::setprecision(2);
  476. std::cout << performance_sampler->mean_frame_duration() * 1000.0 << "\n";
  477. }
  478. ++frame;
  479. */
  480. }
  481. void application::render(double alpha)
  482. {
  483. /*
  484. std::cout << std::fixed;
  485. std::cout << std::setprecision(2);
  486. std::cout << performance_sampler->mean_frame_duration() * 1000.0 << std::endl;
  487. */
  488. if (render_callback)
  489. {
  490. render_callback(alpha);
  491. }
  492. SDL_GL_SwapWindow(sdl_window);
  493. }
  494. void application::translate_sdl_events()
  495. {
  496. // Mouse motion event accumulators
  497. bool mouse_motion = false;
  498. int mouse_x;
  499. int mouse_y;
  500. int mouse_dx = 0;
  501. int mouse_dy = 0;
  502. SDL_Event sdl_event;
  503. while (SDL_PollEvent(&sdl_event))
  504. {
  505. switch (sdl_event.type)
  506. {
  507. case SDL_KEYDOWN:
  508. case SDL_KEYUP:
  509. {
  510. if (sdl_event.key.repeat == 0)
  511. {
  512. input::scancode scancode = input::scancode::unknown;
  513. if (sdl_event.key.keysym.scancode <= SDL_SCANCODE_APP2)
  514. {
  515. scancode = input::sdl_scancode_table[sdl_event.key.keysym.scancode];
  516. }
  517. if (sdl_event.type == SDL_KEYDOWN)
  518. keyboard->press(scancode);
  519. else
  520. keyboard->release(scancode);
  521. }
  522. break;
  523. }
  524. case SDL_MOUSEMOTION:
  525. {
  526. // More than one mouse motion event is often generated per frame, and may be a source of lag.
  527. // Mouse events are accumulated here to prevent excess function calls and allocations
  528. mouse_motion = true;
  529. mouse_x = sdl_event.motion.x;
  530. mouse_y = sdl_event.motion.y;
  531. mouse_dx += sdl_event.motion.xrel;
  532. mouse_dy += sdl_event.motion.yrel;
  533. break;
  534. }
  535. case SDL_MOUSEBUTTONDOWN:
  536. {
  537. mouse->press(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  538. break;
  539. }
  540. case SDL_MOUSEBUTTONUP:
  541. {
  542. mouse->release(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  543. break;
  544. }
  545. case SDL_MOUSEWHEEL:
  546. {
  547. int direction = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1;
  548. mouse->scroll(sdl_event.wheel.x * direction, sdl_event.wheel.y * direction);
  549. break;
  550. }
  551. case SDL_CONTROLLERBUTTONDOWN:
  552. {
  553. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  554. {
  555. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  556. {
  557. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  558. it->second->press(button);
  559. }
  560. }
  561. break;
  562. }
  563. case SDL_CONTROLLERBUTTONUP:
  564. {
  565. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  566. {
  567. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  568. {
  569. input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
  570. it->second->release(button);
  571. }
  572. }
  573. break;
  574. }
  575. case SDL_CONTROLLERAXISMOTION:
  576. {
  577. if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
  578. {
  579. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  580. {
  581. input::gamepad_axis axis = input::sdl_axis_table[sdl_event.caxis.axis];
  582. float value = sdl_event.caxis.value;
  583. static const float min = static_cast<float>(std::numeric_limits<std::int16_t>::min());
  584. static const float max = static_cast<float>(std::numeric_limits<std::int16_t>::max());
  585. value /= (value < 0.0f) ? -min : max;
  586. it->second->move(axis, value);
  587. }
  588. }
  589. break;
  590. }
  591. case SDL_CONTROLLERDEVICEADDED:
  592. {
  593. if (SDL_IsGameController(sdl_event.cdevice.which))
  594. {
  595. SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
  596. std::string controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  597. if (sdl_controller)
  598. {
  599. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  600. {
  601. logger->log("Reconnected gamepad \"" + controller_name + "\"");
  602. it->second->connect(true);
  603. }
  604. else
  605. {
  606. // Get gamepad GUID
  607. SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
  608. SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
  609. char guid_buffer[33];
  610. std::memset(guid_buffer, '\0', 33);
  611. SDL_JoystickGetGUIDString(sdl_guid, &guid_buffer[0], 33);
  612. std::string guid(guid_buffer);
  613. logger->log("Connected gamepad \"" + controller_name + "\" with GUID: " + guid + "");
  614. // Create new gamepad
  615. input::gamepad* gamepad = new input::gamepad();
  616. gamepad->set_event_dispatcher(event_dispatcher);
  617. gamepad->set_guid(guid);
  618. // Add gamepad to list and map
  619. gamepads.push_back(gamepad);
  620. gamepad_map[sdl_event.cdevice.which] = gamepad;
  621. // Send controller connected event
  622. gamepad->connect(false);
  623. }
  624. }
  625. else
  626. {
  627. logger->error("Failed to connected gamepad \"" + controller_name + "\"");
  628. }
  629. }
  630. break;
  631. }
  632. case SDL_CONTROLLERDEVICEREMOVED:
  633. {
  634. SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
  635. if (sdl_controller)
  636. {
  637. SDL_GameControllerClose(sdl_controller);
  638. logger->log("Disconnected gamepad");
  639. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  640. {
  641. it->second->disconnect();
  642. }
  643. }
  644. break;
  645. }
  646. case SDL_WINDOWEVENT:
  647. {
  648. if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED)
  649. {
  650. window_resized();
  651. }
  652. break;
  653. }
  654. case SDL_QUIT:
  655. {
  656. close(EXIT_SUCCESS);
  657. break;
  658. }
  659. }
  660. }
  661. // Process accumulated mouse motion events
  662. if (mouse_motion)
  663. {
  664. mouse->move(mouse_x, mouse_y, mouse_dx, mouse_dy);
  665. }
  666. }
  667. void application::window_resized()
  668. {
  669. // Update window size and viewport size
  670. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  671. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  672. rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
  673. window_resized_event event;
  674. event.w = window_dimensions[0];
  675. event.h = window_dimensions[1];
  676. event_dispatcher->queue(event);
  677. }