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

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