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

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