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

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