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

649 lines
16 KiB

  1. /*
  2. * Copyright (C) 2020 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 "input/keyboard.hpp"
  29. #include "input/mouse.hpp"
  30. #include "input/game-controller.hpp"
  31. #include "rasterizer/rasterizer.hpp"
  32. #include "resources/image.hpp"
  33. #include <SDL2/SDL.h>
  34. #include <glad/glad.h>
  35. #include <stdexcept>
  36. #include <utility>
  37. #include <thread>
  38. #include <stb/stb_image_write.h>
  39. application::application():
  40. closed(false),
  41. exit_status(EXIT_SUCCESS),
  42. state({nullptr, nullptr}),
  43. update_callback(nullptr),
  44. render_callback(nullptr),
  45. fullscreen(true),
  46. vsync(true),
  47. cursor_visible(true),
  48. display_dimensions({0, 0}),
  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 ::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_DOUBLEBUFFER, 1);
  95. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  96. // Get display dimensions
  97. SDL_DisplayMode sdl_desktop_display_mode;
  98. if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0)
  99. {
  100. logger->error("Failed to get desktop display mode: \"" + std::string(SDL_GetError()) + "\"");
  101. throw std::runtime_error("Failed to detect desktop display mode");
  102. }
  103. else
  104. {
  105. logger->log("Detected " + std::to_string(sdl_desktop_display_mode.w) + "x" + std::to_string(sdl_desktop_display_mode.h) + " display");
  106. display_dimensions = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
  107. }
  108. // Create a hidden fullscreen window
  109. logger->push_task("Creating " + std::to_string(display_dimensions[0]) + "x" + std::to_string(display_dimensions[1]) + " window");
  110. sdl_window = SDL_CreateWindow
  111. (
  112. "",
  113. SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
  114. display_dimensions[0], display_dimensions[1],
  115. SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN
  116. );
  117. if (!sdl_window)
  118. {
  119. logger->pop_task(EXIT_FAILURE);
  120. throw std::runtime_error("Failed to create SDL window");
  121. }
  122. else
  123. {
  124. logger->pop_task(EXIT_SUCCESS);
  125. }
  126. // Create OpenGL context
  127. logger->push_task("Creating OpenGL 3.3 context");
  128. sdl_gl_context = SDL_GL_CreateContext(sdl_window);
  129. if (!sdl_gl_context)
  130. {
  131. logger->pop_task(EXIT_FAILURE);
  132. throw std::runtime_error("Failed to create OpenGL context");
  133. }
  134. else
  135. {
  136. logger->pop_task(EXIT_SUCCESS);
  137. }
  138. // Update window size and viewport size
  139. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  140. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  141. // Load OpenGL functions via GLAD
  142. logger->push_task("Loading OpenGL functions");
  143. if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
  144. {
  145. logger->pop_task(EXIT_FAILURE);
  146. throw std::runtime_error("Failed to load OpenGL functions");
  147. }
  148. else
  149. {
  150. logger->pop_task(EXIT_SUCCESS);
  151. }
  152. // Set v-sync mode
  153. int swap_interval = (vsync) ? 1 : 0;
  154. logger->push_task((swap_interval) ? "Enabling v-sync" : "Disabling v-sync");
  155. if (SDL_GL_SetSwapInterval(swap_interval) != 0)
  156. {
  157. logger->pop_task(EXIT_FAILURE);
  158. }
  159. else
  160. {
  161. logger->pop_task(EXIT_SUCCESS);
  162. }
  163. // Init SDL joystick and game controller subsystems
  164. logger->push_task("Initializing SDL Joystick and Game Controller subsystems");
  165. if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
  166. {
  167. logger->pop_task(EXIT_FAILURE);
  168. }
  169. else
  170. {
  171. logger->pop_task(EXIT_SUCCESS);
  172. }
  173. // Load SDL game controller mappings
  174. /*
  175. logger->push_task("Loading SDL game controller mappings from database");
  176. std::string gamecontrollerdb_path = data_path + "controls/gamecontrollerdb.txt";
  177. if (SDL_GameControllerAddMappingsFromFile(gamecontrollerdb_path.c_str()) == -1)
  178. {
  179. logger->pop_task(EXIT_FAILURE);
  180. }
  181. else
  182. {
  183. logger->pop_task(EXIT_SUCCESS);
  184. }
  185. */
  186. // Setup rasterizer
  187. rasterizer = new ::rasterizer();
  188. // Setup events
  189. event_dispatcher = new ::event_dispatcher();
  190. // Setup input
  191. keyboard = new ::keyboard();
  192. keyboard->set_event_dispatcher(event_dispatcher);
  193. mouse = new ::mouse();
  194. mouse->set_event_dispatcher(event_dispatcher);
  195. // Setup frame scheduler
  196. frame_scheduler = new ::frame_scheduler();
  197. frame_scheduler->set_update_callback(std::bind(&application::update, this, std::placeholders::_1, std::placeholders::_2));
  198. frame_scheduler->set_render_callback(std::bind(&application::render, this, std::placeholders::_1));
  199. frame_scheduler->set_update_rate(update_rate);
  200. frame_scheduler->set_max_frame_duration(0.25);
  201. // Setup performance sampling
  202. performance_sampler = new ::performance_sampler();
  203. performance_sampler->set_sample_size(15);
  204. }
  205. application::~application()
  206. {
  207. // Destroy the OpenGL context
  208. SDL_GL_DeleteContext(sdl_gl_context);
  209. // Destroy the SDL window
  210. SDL_DestroyWindow(sdl_window);
  211. // Shutdown SDL
  212. SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
  213. SDL_Quit();
  214. }
  215. void application::close(int status)
  216. {
  217. closed = true;
  218. exit_status = status;
  219. }
  220. int application::execute(bootloader_type bootloader)
  221. {
  222. // Execute bootloader
  223. if (bootloader)
  224. {
  225. exit_status = bootloader(this);
  226. if (exit_status != EXIT_SUCCESS)
  227. {
  228. return exit_status;
  229. }
  230. }
  231. // Show window
  232. SDL_ShowWindow(sdl_window);
  233. // Clear window
  234. rasterizer->clear_framebuffer(true, false, false);
  235. SDL_GL_SwapWindow(sdl_window);
  236. // Perform initial update
  237. update(0.0, 0.0);
  238. // Reset frame scheduler
  239. frame_scheduler->reset();
  240. // Schedule frames until closed
  241. while (!closed)
  242. {
  243. // Tick frame scheduler
  244. frame_scheduler->tick();
  245. // Sample frame duration
  246. performance_sampler->sample(frame_scheduler->get_frame_duration());
  247. }
  248. // Exit current state
  249. change_state({nullptr, nullptr});
  250. return exit_status;
  251. }
  252. void application::change_state(const state_type& next_state)
  253. {
  254. // Exit current state
  255. if (state[1])
  256. {
  257. state[1]();
  258. }
  259. // Enter next state
  260. state = next_state;
  261. if (state[0])
  262. {
  263. state[0]();
  264. }
  265. }
  266. std::shared_ptr<image> application::capture_frame() const
  267. {
  268. int w = viewport_dimensions[0];
  269. int h = viewport_dimensions[1];
  270. std::shared_ptr<image> frame = std::make_shared<image>();
  271. frame->format(3, false);
  272. frame->resize(w, h);
  273. // Read pixel data from framebuffer into image
  274. glReadBuffer(GL_BACK);
  275. glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, frame->get_pixels());
  276. return std::move(frame);
  277. }
  278. void application::save_frame(const std::string& path) const
  279. {
  280. logger->push_task("Saving screenshot to \"" + path + "\"");
  281. auto frame = capture_frame();
  282. std::thread
  283. (
  284. [frame, path]
  285. {
  286. stbi_flip_vertically_on_write(1);
  287. stbi_write_png(path.c_str(), frame->get_width(), frame->get_height(), frame->get_channels(), frame->get_pixels(), frame->get_width() * frame->get_channels());
  288. }
  289. ).detach();
  290. logger->pop_task(EXIT_SUCCESS);
  291. }
  292. void application::set_update_callback(const update_callback_type& callback)
  293. {
  294. update_callback = callback;
  295. }
  296. void application::set_render_callback(const render_callback_type& callback)
  297. {
  298. render_callback = callback;
  299. }
  300. void application::set_update_rate(double frequency)
  301. {
  302. update_rate = frequency;
  303. frame_scheduler->set_update_rate(update_rate);
  304. }
  305. void application::set_title(const std::string& title)
  306. {
  307. SDL_SetWindowTitle(sdl_window, title.c_str());
  308. }
  309. void application::set_cursor_visible(bool visible)
  310. {
  311. SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE);
  312. cursor_visible = visible;
  313. }
  314. void application::set_relative_mouse_mode(bool enabled)
  315. {
  316. if (enabled)
  317. {
  318. SDL_GetMouseState(&mouse_position[0], &mouse_position[1]);
  319. SDL_ShowCursor(SDL_DISABLE);
  320. SDL_SetRelativeMouseMode(SDL_TRUE);
  321. }
  322. else
  323. {
  324. SDL_SetRelativeMouseMode(SDL_FALSE);
  325. SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]);
  326. if (cursor_visible)
  327. SDL_ShowCursor(SDL_ENABLE);
  328. }
  329. }
  330. void application::resize_window(int width, int height)
  331. {
  332. int x = (display_dimensions[0] >> 1) - (width >> 1);
  333. int y = (display_dimensions[1] >> 1) - (height >> 1);
  334. // Resize and center window
  335. SDL_SetWindowPosition(sdl_window, x, y);
  336. SDL_SetWindowSize(sdl_window, width, height);
  337. window_resized();
  338. }
  339. void application::set_fullscreen(bool fullscreen)
  340. {
  341. if (this->fullscreen != fullscreen)
  342. {
  343. this->fullscreen = fullscreen;
  344. if (fullscreen)
  345. {
  346. SDL_HideWindow(sdl_window);
  347. SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
  348. SDL_ShowWindow(sdl_window);
  349. }
  350. else
  351. {
  352. SDL_SetWindowFullscreen(sdl_window, 0);
  353. SDL_SetWindowBordered(sdl_window, SDL_TRUE);
  354. SDL_SetWindowResizable(sdl_window, SDL_TRUE);
  355. }
  356. }
  357. }
  358. void application::set_vsync(bool vsync)
  359. {
  360. if (this->vsync != vsync)
  361. {
  362. this->vsync = vsync;
  363. SDL_GL_SetSwapInterval((vsync) ? 1 : 0);
  364. }
  365. }
  366. void application::set_window_opacity(float opacity)
  367. {
  368. SDL_SetWindowOpacity(sdl_window, opacity);
  369. }
  370. void application::swap_buffers()
  371. {
  372. SDL_GL_SwapWindow(sdl_window);
  373. }
  374. void application::update(double t, double dt)
  375. {
  376. translate_sdl_events();
  377. event_dispatcher->update(t);
  378. if (update_callback)
  379. {
  380. update_callback(t, dt);
  381. }
  382. }
  383. void application::render(double alpha)
  384. {
  385. /*
  386. std::cout << std::fixed;
  387. std::cout << std::setprecision(2);
  388. std::cout << performance_sampler.mean_frame_duration() * 1000.0 << std::endl;
  389. */
  390. if (render_callback)
  391. {
  392. render_callback(alpha);
  393. }
  394. SDL_GL_SwapWindow(sdl_window);
  395. }
  396. void application::translate_sdl_events()
  397. {
  398. SDL_Event sdl_event;
  399. while (SDL_PollEvent(&sdl_event))
  400. {
  401. switch (sdl_event.type)
  402. {
  403. case SDL_KEYDOWN:
  404. case SDL_KEYUP:
  405. {
  406. if (sdl_event.key.repeat == 0)
  407. {
  408. scancode scancode = scancode::unknown;
  409. if (sdl_event.key.keysym.scancode <= SDL_SCANCODE_APP2)
  410. {
  411. scancode = sdl_scancode_table[sdl_event.key.keysym.scancode];
  412. }
  413. if (sdl_event.type == SDL_KEYDOWN)
  414. keyboard->press(scancode);
  415. else
  416. keyboard->release(scancode);
  417. }
  418. break;
  419. }
  420. case SDL_MOUSEMOTION:
  421. {
  422. mouse->move(sdl_event.motion.x, sdl_event.motion.y, sdl_event.motion.xrel, sdl_event.motion.yrel);
  423. break;
  424. }
  425. case SDL_MOUSEBUTTONDOWN:
  426. {
  427. mouse->press(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  428. break;
  429. }
  430. case SDL_MOUSEBUTTONUP:
  431. {
  432. mouse->release(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  433. break;
  434. }
  435. case SDL_MOUSEWHEEL:
  436. {
  437. int direction = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1;
  438. mouse->scroll(sdl_event.wheel.x * direction, sdl_event.wheel.y * direction);
  439. break;
  440. }
  441. case SDL_CONTROLLERBUTTONDOWN:
  442. {
  443. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  444. {
  445. if (auto it = game_controller_map.find(sdl_event.cdevice.which); it != game_controller_map.end())
  446. {
  447. game_controller_button button = sdl_button_table[sdl_event.cbutton.button];
  448. it->second->press(button);
  449. }
  450. }
  451. break;
  452. }
  453. case SDL_CONTROLLERBUTTONUP:
  454. {
  455. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  456. {
  457. if (auto it = game_controller_map.find(sdl_event.cdevice.which); it != game_controller_map.end())
  458. {
  459. game_controller_button button = sdl_button_table[sdl_event.cbutton.button];
  460. it->second->release(button);
  461. }
  462. }
  463. break;
  464. }
  465. case SDL_CONTROLLERAXISMOTION:
  466. {
  467. if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
  468. {
  469. if (auto it = game_controller_map.find(sdl_event.cdevice.which); it != game_controller_map.end())
  470. {
  471. game_controller_axis axis = sdl_axis_table[sdl_event.caxis.axis];
  472. float value = sdl_event.caxis.value;
  473. value /= (value < 0.0f) ? 32768.0f : 32767.0f;
  474. it->second->move(axis, value);
  475. }
  476. }
  477. break;
  478. }
  479. case SDL_CONTROLLERDEVICEADDED:
  480. {
  481. if (SDL_IsGameController(sdl_event.cdevice.which))
  482. {
  483. SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
  484. std::string controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  485. if (sdl_controller)
  486. {
  487. if (auto it = game_controller_map.find(sdl_event.cdevice.which); it != game_controller_map.end())
  488. {
  489. logger->log("Reconnected game controller \"" + controller_name + "\"");
  490. it->second->connect(true);
  491. }
  492. else
  493. {
  494. logger->log("Connected game controller \"" + controller_name + "\"");
  495. game_controller* controller = new game_controller();
  496. controller->set_event_dispatcher(event_dispatcher);
  497. game_controllers.push_back(controller);
  498. game_controller_map[sdl_event.cdevice.which] = controller;
  499. controller->connect(false);
  500. }
  501. }
  502. else
  503. {
  504. logger->error("Failed to connected game controller \"" + controller_name + "\"");
  505. }
  506. }
  507. break;
  508. }
  509. case SDL_CONTROLLERDEVICEREMOVED:
  510. {
  511. SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
  512. if (sdl_controller)
  513. {
  514. SDL_GameControllerClose(sdl_controller);
  515. logger->log("Disconnected game controller");
  516. if (auto it = game_controller_map.find(sdl_event.cdevice.which); it != game_controller_map.end())
  517. {
  518. it->second->disconnect();
  519. }
  520. }
  521. break;
  522. }
  523. case SDL_WINDOWEVENT:
  524. {
  525. if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED)
  526. {
  527. window_resized();
  528. }
  529. break;
  530. }
  531. case SDL_QUIT:
  532. {
  533. close(EXIT_SUCCESS);
  534. break;
  535. }
  536. }
  537. }
  538. }
  539. void application::window_resized()
  540. {
  541. // Update window size and viewport size
  542. SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
  543. SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
  544. rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
  545. window_resized_event event;
  546. event.w = window_dimensions[0];
  547. event.h = window_dimensions[1];
  548. event_dispatcher->queue(event);
  549. /*
  550. rasterizer->window_resized(width, height);
  551. overworld_camera.set_perspective(overworld_camera.get_fov(), aspect_ratio, overworld_camera.get_clip_near(), overworld_camera.get_clip_far());
  552. underworld_camera.set_perspective(underworld_camera.get_fov(), aspect_ratio, underworld_camera.get_clip_near(), underworld_camera.get_clip_far());
  553. control_system->set_viewport(viewport);
  554. camera_system->set_viewport(viewport);
  555. tool_system->set_viewport(viewport);
  556. ui_system->set_viewport(viewport);
  557. */
  558. }