frame timing updated to run at maximum allowable framerate, delay between frame calls configurable, vsync flag configurable, max framerate configurable up to unlimited

This commit is contained in:
ohsqueezy 2023-09-07 19:09:04 -04:00
parent c0fe0c782d
commit 4c3235c2ff
5 changed files with 44 additions and 45 deletions

View File

@ -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},

View File

@ -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<std::string>().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<bool>();
if (SDL_GL_SetSwapInterval(static_cast<int>(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<TTF_Font> 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<int>();
/* 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)

View File

@ -62,7 +62,6 @@ protected:
private:
float frame_length = 1000.0 / 60.0;
SDL_Window* _window;
std::shared_ptr<TTF_Font> _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"; }

View File

@ -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<sb::Attributes> uv = std::make_shared<sb::Attributes>(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<sb::Attributes> color = std::make_shared<sb::Attributes>(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<std::string, std::shared_ptr<sb::Attributes>>({{"position", position}, {"uv", uv}, {"color", color}})) {}

View File

@ -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 */