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

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