From 4c3235c2ffd35ebcfaacca3cd0c417826c363e74 Mon Sep 17 00:00:00 2001 From: frank Date: Thu, 7 Sep 2023 19:09:04 -0400 Subject: [PATCH] frame timing updated to run at maximum allowable framerate, delay between frame calls configurable, vsync flag configurable, max framerate configurable up to unlimited --- src/Configuration.cpp | 9 ++++-- src/Game.cpp | 65 +++++++++++++++++++++---------------------- src/Game.hpp | 2 -- src/Model.hpp | 8 +++--- src/Recorder.cpp | 5 ++-- 5 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/Configuration.cpp b/src/Configuration.cpp index fe2855b..f117cf4 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -39,7 +39,9 @@ void Configuration::set_defaults() }; config["display"] = { {"dimensions", {960, 540}}, - {"framerate", 60}, + {"max framerate", -1}, + {"vsync", false}, + {"sdl delay", 6}, {"title", "[SPACEBOX]"}, {"debug", false}, {"show-cursor", false}, @@ -65,14 +67,15 @@ void Configuration::set_defaults() }, config["recording"] = { {"enabled", false}, + {"video frame length", 60.0f}, {"screenshot-prefix", "screenshot-"}, {"screenshot-extension", ".png"}, {"screenshot-zfill", 5}, {"screenshot-directory", "."}, - {"gif-frame-length", 0.1}, + {"gif-frame-length", 0.1f}, {"video-directory", "."}, {"write-mp4", false}, - {"max-stash-length", 5.0}, + {"max-stash-length", 5.0f}, {"max-in-game-stashes", 3}, {"max-video-stashes", 40}, {"max-video-memory", 1000}, diff --git a/src/Game.cpp b/src/Game.cpp index 8de38a7..a13c592 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -76,9 +76,8 @@ Game::Game() /* Tell SDL which render driver you will be requesting when calling SDL_CreateRenderer */ SDL_SetHint(SDL_HINT_RENDER_DRIVER, configuration()["display"]["render driver"].get().c_str()); - /* Initialize the buffer of frame lengths which will be used to calculate FPS */ + /* Initialize the buffer of frame lengths which will be used to calculate and display FPS */ frame_length_history.reserve(5000); - set_framerate(configuration()["display"]["framerate"]); /* Subscribe to SDL's quit event */ _delegate.subscribe(&Game::handle_quit_event, this, SDL_QUIT); @@ -255,14 +254,18 @@ void Game::load_gl_context() sb::Log::sdl_error("Could not get GL context"); flag_to_end(); } - /* try enabling vsync */ - if (SDL_GL_SetSwapInterval(1) == 0) + + /* Try setting vsync */ + bool vsync_enabled = configuration()("display", "vsync").get(); + if (SDL_GL_SetSwapInterval(static_cast(vsync_enabled)) == 0) { - sb::Log::log("enabled vsync"); + std::ostringstream message; + message << "Set vsync to " << vsync_enabled; + sb::Log::log(message); } else { - sb::Log::log("vsync not supported"); + sb::Log::log("Setting vysnc is not supported"); } /* Android does not use GLEW*/ @@ -570,35 +573,51 @@ std::shared_ptr sb::Game::font(const fs::path& path, int size) const void Game::run() { + /* Any events received before this discarded. This prevents input from being entered before the game starts. Input is also suspended + * for a moment in case something is input as the screen is appearing. */ SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); suppress_input_temporarily(); + + /* In Emscripten builds, use the browser's requestAnimationFrame to call each frame until the game is quit. */ #if defined(__EMSCRIPTEN__) SDL_Log("using emscripten main loop"); emscripten_set_main_loop_arg(&loop, this, -1, true); + + /* Other builds request a frame repeatedly until the game quits, delaying for the configured amount of milliseconds between each request. */ #else SDL_Log("using standard main loop"); while (!done) { frame(SDL_GetTicks()); - SDL_Delay(8); + SDL_Delay(configuration()("display", "sdl delay")); } #endif } void Game::frame(float timestamp) { - if (timestamp - last_frame_timestamp + frame_time_overflow >= frame_length) + /* Max framerate is the maximum number of frames to display per second. It is unlimited if the value is -1. */ + int max_framerate = configuration()("display", "max framerate").get(); + + /* If max framerate is unlimited, always build a frame. Otherwise, check if enough milliseconds have passed since the last frame. */ + if (max_framerate == -1 || timestamp - last_frame_timestamp >= 1000.0f / max_framerate) { + /* Amount of time passed since the last timestamp was recorded in milliseconds. */ last_frame_length = timestamp - last_frame_timestamp; + + /* Constrain the size of the frame length history and add the milliseconds between this frame and the last. */ if (frame_length_history.size() == 5000) { frame_length_history.pop_back(); } frame_length_history.insert(frame_length_history.begin(), last_frame_length); - frame_time_overflow = last_frame_length + frame_time_overflow - frame_length; + + /* Save the timestamp */ last_frame_timestamp = timestamp; - if (last_frame_length < 1000) - { + + // /* Only build a frame if the last frame was under a second. */ + // if (last_frame_length < 1000) + // { float timestamp_seconds = timestamp / 1000.0f; // recorder.update(timestamp_seconds); _delegate.dispatch(); @@ -611,12 +630,9 @@ void Game::frame(float timestamp) SDL_SetRenderTarget(renderer, nullptr); SDL_RenderPresent(renderer); } - } - // if (frame_time_overflow > frame_length) - // { - // SDL_Log("%i frame(s) dropped", ((int) (frame_time_overflow / frame_length))); - // frame_time_overflow = 0; // } + + /* Update frame count per second for verbose log */ frame_count_this_second++; if (timestamp - last_frame_count_timestamp >= 1000) { @@ -649,23 +665,6 @@ void Game::flag_to_end() done = true; } -/* Set the length of a frame in seconds by passing the framerate, the amount of frames to display - * per second */ -void Game::set_framerate(int framerate) -{ - if (framerate < 1) - { - framerate = 1; - } - frame_length = 1000.0 / framerate; -} - -/* Return the length of a frame in seconds */ -float Game::get_frame_length() const -{ - return frame_length; -} - void Game::handle_quit_event(SDL_Event &event) { if (event.type == SDL_QUIT) diff --git a/src/Game.hpp b/src/Game.hpp index e375c27..22455fa 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -62,7 +62,6 @@ protected: private: - float frame_length = 1000.0 / 60.0; SDL_Window* _window; std::shared_ptr _font; @@ -165,7 +164,6 @@ public: void flag_to_end(); virtual void update(float timestamp) = 0; void set_framerate(int); - float get_frame_length() const; void handle_quit_event(SDL_Event&); void quit(); virtual std::string class_name() const { return "Game"; } diff --git a/src/Model.hpp b/src/Model.hpp index 1506385..32b58c8 100644 --- a/src/Model.hpp +++ b/src/Model.hpp @@ -327,16 +327,16 @@ namespace sb {1.0f, 1.0f}, {1.0f, -1.0f}, {-1.0f, -1.0f} }); - /* Map a texture to fill the plane */ + /* Create texture map that fills the plane */ inline const static std::shared_ptr uv = std::make_shared(sb::Attributes{ {0.0f, 1.0f}, {1.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f} }); - /* A gradient from magenta to yellow */ + /* Use transparent black color so it can easily be added to in the shader */ inline const static std::shared_ptr color = std::make_shared(sb::Attributes{ - {1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 0.0f, 1.0f}, {0.8f, 0.0f, 0.3f, 1.0f}, - {1.0f, 1.0f, 0.0f, 1.0f}, {0.8f, 0.0f, 0.3f, 1.0f}, {0.8f, 0.0f, 0.3f, 1.0f} + {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f} }); Plane() : Model(std::map>({{"position", position}, {"uv", uv}, {"color", color}})) {} diff --git a/src/Recorder.cpp b/src/Recorder.cpp index 5287663..2850b79 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -10,11 +10,10 @@ Recorder::Recorder(Node* parent) : Node(parent) Mix_SetPostMix(Recorder::process_audio, this); } -/* Returns length of a recorded video frame in seconds. Defaults to the frame length of the game if this hasn't - * been configured by the user. */ +/* Returns length of a recorded video frame in seconds */ float Recorder::frame_length() { - return configuration()["recording"].value("video-frame-length", get_root()->get_frame_length() / 1000.0f); + return configuration()["recording"]["video frame length"]; } /* Handle commands for screenshot, record video and save video */