another desperate attempt at an accurate fps limiter
This commit is contained in:
parent
7ba1099902
commit
05478cd543
17 changed files with 368 additions and 175 deletions
|
@ -42,6 +42,14 @@ In addition to the variables listed here, those processed by our runtime depende
|
|||
|
||||
* **TAISEI_GL_EXT_OVERRIDES** *(default: unset)*: Space-separated list of OpenGL extensions that are assumed to be supported, even if the driver says they aren't. Prefix an extension with `-` to invert this behaviour. Might be used to work around bugs in some weird/ancient/broken drivers, but your chances are slim. Also note that Taisei assumes many extensions to be available on any sane OpenGL 2.1+ implementation and doesn't test for them, so you can't disable code that uses those this way.
|
||||
|
||||
### Timing
|
||||
|
||||
* **TAISEI_HIRES_TIMER** *(default value: `1`)*: if `1`, try to use the system's high resolution timer to limit the game's framerate. Disabling this is not recommended; it will likely make Taisei run slower or faster than intended and the reported FPS will be less accurate.
|
||||
|
||||
* **TAISEI_FRAMELIMITER_SLEEP** *(default value: `10`)*: if over `0`, try to sleep this many milliseconds after every frame if it was processed quickly enough. This reduces CPU usage by having the game spend less time in a busy loop, but may hurt framerate stability if set too high, especially if the high resolution timer is disabled or not available. The default value should be reasonable.
|
||||
|
||||
* **TAISEI_FRAMELIMITER_SLEEP_EXACT** *(default value: `1`)*: if `1`, the framerate limiter will either try to sleep the exact amount of time set in `TAISEI_FRAMELIMITER_SLEEP`, or none at all. Mitigates the aforementioned framerate stability issues by effectively making `TAISEI_FRAMELIMITER_SLEEP` do nothing if the value is too high for your system. This should be a safe default.
|
||||
|
||||
### Logging
|
||||
|
||||
Taisei's logging system currently has four basic levels and works by dispatching messages to a few output handlers. Each handler has a level filter, which is configured by a separate environment variable. All of those variables work the same way: their value looks like an IRC mode string, and represents a modification of the handler's default settings. If this doesn't make sense, take a look at the *Examples* section.
|
||||
|
|
|
@ -69,6 +69,7 @@ set(SRCs
|
|||
color.c
|
||||
difficulty.c
|
||||
audio_common.c
|
||||
hirestime.c
|
||||
menu/menu.c
|
||||
menu/mainmenu.c
|
||||
menu/options.c
|
||||
|
@ -213,6 +214,7 @@ set(LIBDIRs
|
|||
)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-D__USE_MINGW_ANSI_STDIO)
|
||||
set(LIBs ${LIBs} -ldxguid -lwinmm)
|
||||
|
||||
string(REPLACE "gcc" "windres" CMAKE_RC_COMPILER_INIT ${CMAKE_C_COMPILER})
|
||||
|
|
25
src/compat.h
Normal file
25
src/compat.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2017, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2017, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __GNUC__ // clang defines this too
|
||||
#define __attribute__(...)
|
||||
#define __extension__
|
||||
#define PRAGMA(p)
|
||||
#else
|
||||
#define PRAGMA(p) _Pragma(#p)
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW_PRINTF_FORMAT
|
||||
#define FORMAT_ATTR __MINGW_PRINTF_FORMAT
|
||||
#else
|
||||
#define FORMAT_ATTR printf
|
||||
#endif
|
||||
|
||||
void shut_up_stupid_warning(void);
|
|
@ -263,16 +263,18 @@ void credits_preload(void) {
|
|||
NULL);
|
||||
}
|
||||
|
||||
static bool credits_frame(void *arg) {
|
||||
events_poll(NULL, 0);
|
||||
credits_process();
|
||||
credits_draw();
|
||||
global.frames++;
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
return !credits.end;
|
||||
}
|
||||
|
||||
void credits_loop(void) {
|
||||
credits_preload();
|
||||
credits_init();
|
||||
while(credits.end) {
|
||||
events_poll(NULL, 0);
|
||||
credits_process();
|
||||
credits_draw();
|
||||
global.frames++;
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
limit_frame_rate(&global.lasttime);
|
||||
}
|
||||
loop_at_fps(credits_frame, NULL, NULL, FPS);
|
||||
credits_free();
|
||||
}
|
||||
|
|
42
src/ending.c
42
src/ending.c
|
@ -135,29 +135,31 @@ void ending_preload(void) {
|
|||
preload_resource(RES_BGM, "ending", RESF_OPTIONAL);
|
||||
}
|
||||
|
||||
void ending_loop(void) {
|
||||
Ending e;
|
||||
static bool ending_frame(void *arg) {
|
||||
Ending *e = arg;
|
||||
|
||||
ending_preload();
|
||||
create_ending(&e);
|
||||
events_poll(NULL, 0);
|
||||
ending_draw(e);
|
||||
global.frames++;
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
|
||||
global.frames = 0;
|
||||
set_ortho();
|
||||
|
||||
while(e.pos < e.count-1) {
|
||||
events_poll(NULL, 0);
|
||||
|
||||
ending_draw(&e);
|
||||
global.frames++;
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
limit_frame_rate(&global.lasttime);
|
||||
|
||||
if(global.frames >= e.entries[e.pos+1].time)
|
||||
e.pos++;
|
||||
|
||||
if(global.frames == e.entries[e.count-1].time-ENDING_FADE_OUT)
|
||||
set_transition(TransFadeWhite, ENDING_FADE_OUT, ENDING_FADE_OUT);
|
||||
if(global.frames >= e->entries[e->pos+1].time) {
|
||||
e->pos++;
|
||||
}
|
||||
|
||||
if(global.frames == e->entries[e->count-1].time-ENDING_FADE_OUT) {
|
||||
set_transition(TransFadeWhite, ENDING_FADE_OUT, ENDING_FADE_OUT);
|
||||
}
|
||||
|
||||
return e->pos >= e->count - 1;
|
||||
}
|
||||
|
||||
void ending_loop(void) {
|
||||
Ending e;
|
||||
ending_preload();
|
||||
create_ending(&e);
|
||||
global.frames = 0;
|
||||
set_ortho();
|
||||
loop_at_fps(ending_frame, NULL, &e, FPS);
|
||||
free_ending(&e);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
#include "audio.h"
|
||||
#include "rwops/all.h"
|
||||
#include "cli.h"
|
||||
#include "hirestime.h"
|
||||
#include "log.h"
|
||||
|
||||
enum {
|
||||
// defaults
|
||||
|
@ -90,9 +92,9 @@ typedef struct {
|
|||
|
||||
Projectile *particles;
|
||||
|
||||
int frames;
|
||||
uint64_t lasttime; // frame limiter
|
||||
int timer;
|
||||
int frames; // stage global timer
|
||||
int timer; // stage event timer (freezes on bosses, dialogs, etc.)
|
||||
|
||||
int frameskip;
|
||||
|
||||
Boss *boss;
|
||||
|
|
85
src/hirestime.c
Normal file
85
src/hirestime.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2017, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2017, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#include "util.h"
|
||||
|
||||
static bool use_hires;
|
||||
static long double time_current;
|
||||
static long double time_offset;
|
||||
static uint64_t prev_hires_time;
|
||||
static uint64_t prev_hires_freq;
|
||||
static SDL_mutex *paranoia;
|
||||
|
||||
static void time_update(void) {
|
||||
bool retry;
|
||||
|
||||
do {
|
||||
retry = false;
|
||||
|
||||
uint64_t freq = SDL_GetPerformanceFrequency();
|
||||
uint64_t cntr = SDL_GetPerformanceCounter();
|
||||
|
||||
if(freq != prev_hires_freq) {
|
||||
log_debug("High resolution timer frequency changed: was %"PRIu64", now %"PRIu64". Saved time offset: %.16Lf", prev_hires_freq, freq, time_offset);
|
||||
time_offset = time_current;
|
||||
prev_hires_freq = freq;
|
||||
prev_hires_time = SDL_GetPerformanceCounter();
|
||||
retry = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
long double time_new = time_offset + (long double)(cntr - prev_hires_time) / freq;
|
||||
|
||||
if(time_new < time_current) {
|
||||
log_warn("BUG: time went backwards. Was %.16Lf, now %.16Lf. Possible cause: your OS sucks spherical objects. Attempting to correct this...", time_current, time_new);
|
||||
time_offset = time_current;
|
||||
time_current = 0;
|
||||
prev_hires_time = SDL_GetPerformanceCounter();
|
||||
retry = true;
|
||||
} else {
|
||||
time_current = time_new;
|
||||
}
|
||||
} while(retry);
|
||||
}
|
||||
|
||||
void time_init(void) {
|
||||
use_hires = getenvint("TAISEI_HIRES_TIMER", 1);
|
||||
|
||||
if(use_hires) {
|
||||
if(!(paranoia = SDL_CreateMutex())) {
|
||||
log_warn("Not using the system high resolution timer: SDL_CreateMutex() failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("Using the system high resolution timer");
|
||||
prev_hires_time = SDL_GetPerformanceCounter();
|
||||
prev_hires_freq = SDL_GetPerformanceFrequency();
|
||||
} else {
|
||||
log_info("Not using the system high resolution timer: disabled by environment");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void time_shutdown(void) {
|
||||
if(paranoia) {
|
||||
SDL_DestroyMutex(paranoia);
|
||||
paranoia = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
long double time_get(void) {
|
||||
if(use_hires) {
|
||||
SDL_LockMutex(paranoia);
|
||||
time_update();
|
||||
long double t = time_current;
|
||||
SDL_UnlockMutex(paranoia);
|
||||
return t;
|
||||
}
|
||||
|
||||
return SDL_GetTicks() / 1000.0;
|
||||
}
|
13
src/hirestime.h
Normal file
13
src/hirestime.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2017, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2017, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void time_init(void);
|
||||
void time_shutdown(void);
|
||||
long double time_get(void);
|
|
@ -11,6 +11,7 @@
|
|||
#include <stdnoreturn.h>
|
||||
#include <stdbool.h>
|
||||
#include <SDL.h>
|
||||
#include "compat.h"
|
||||
|
||||
typedef enum LogLevel {
|
||||
LOG_NONE = 0,
|
||||
|
@ -92,9 +93,7 @@ bool log_initialized(void);
|
|||
//
|
||||
|
||||
void _taisei_log(LogLevel lvl, bool is_backtrace, const char *funcname, const char *fmt, ...)
|
||||
__attribute__((format(printf, 4, 5)));
|
||||
__attribute__((format(FORMAT_ATTR, 4, 5)));
|
||||
|
||||
noreturn void _taisei_log_fatal(LogLevel lvl, const char *funcname, const char *fmt, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
||||
|
||||
|
||||
__attribute__((format(FORMAT_ATTR, 3, 4)));
|
||||
|
|
|
@ -39,6 +39,7 @@ static void taisei_shutdown(void) {
|
|||
config_uninit();
|
||||
vfs_uninit();
|
||||
events_shutdown();
|
||||
time_shutdown();
|
||||
|
||||
log_info("Good bye");
|
||||
SDL_Quit();
|
||||
|
@ -182,6 +183,7 @@ int main(int argc, char **argv) {
|
|||
log_lib_versions();
|
||||
|
||||
init_sdl();
|
||||
time_init();
|
||||
init_global(&a);
|
||||
events_init();
|
||||
init_fonts();
|
||||
|
|
|
@ -144,6 +144,27 @@ void menu_logic(MenuData *menu) {
|
|||
menu->frames++;
|
||||
}
|
||||
|
||||
static bool menu_frame(void *arg) {
|
||||
MenuData *menu = arg;
|
||||
|
||||
menu->logic(menu);
|
||||
|
||||
if(menu->state != MS_FadeOut || menu->flags & MF_AlwaysProcessInput) {
|
||||
assert(menu->input);
|
||||
menu->input(menu);
|
||||
} else {
|
||||
menu_no_input(menu);
|
||||
}
|
||||
|
||||
assert(menu->draw);
|
||||
menu->draw(menu);
|
||||
draw_and_update_transition();
|
||||
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
|
||||
return menu->state != MS_Dead;
|
||||
}
|
||||
|
||||
int menu_loop(MenuData *menu) {
|
||||
set_ortho();
|
||||
|
||||
|
@ -151,24 +172,8 @@ int menu_loop(MenuData *menu) {
|
|||
menu->begin(menu);
|
||||
}
|
||||
|
||||
while(menu->state != MS_Dead) {
|
||||
assert(menu->logic);
|
||||
menu->logic(menu);
|
||||
|
||||
if(menu->state != MS_FadeOut || menu->flags & MF_AlwaysProcessInput) {
|
||||
assert(menu->input);
|
||||
menu->input(menu);
|
||||
} else {
|
||||
menu_no_input(menu);
|
||||
}
|
||||
|
||||
assert(menu->draw);
|
||||
menu->draw(menu);
|
||||
draw_and_update_transition();
|
||||
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
limit_frame_rate(&menu->lasttime);
|
||||
}
|
||||
assert(menu->logic != NULL);
|
||||
loop_at_fps(menu_frame, NULL, menu, FPS);
|
||||
|
||||
if(menu->end) {
|
||||
menu->end(menu);
|
||||
|
|
|
@ -55,7 +55,6 @@ struct MenuData {
|
|||
int ecount;
|
||||
|
||||
int frames;
|
||||
uint64_t lasttime;
|
||||
|
||||
int state;
|
||||
int transition_in_time;
|
||||
|
|
146
src/stage.c
146
src/stage.c
|
@ -185,8 +185,6 @@ static void stage_start(StageInfo *stage) {
|
|||
global.game_over = 0;
|
||||
global.shake_view = 0;
|
||||
|
||||
fpscounter_reset(&global.fps);
|
||||
|
||||
prepare_player_for_next_stage(&global.plr);
|
||||
|
||||
if(stage->type == STAGE_SPELL) {
|
||||
|
@ -489,6 +487,85 @@ void stage_start_bgm(const char *bgm) {
|
|||
}
|
||||
}
|
||||
|
||||
typedef struct StageFrameState {
|
||||
StageInfo *stage;
|
||||
int transition_delay;
|
||||
uint16_t last_replay_fps;
|
||||
} StageFrameState;
|
||||
|
||||
static bool stage_fpslimit_condition(void *arg) {
|
||||
return (global.replaymode != REPLAY_PLAY || !gamekeypressed(KEY_SKIP)) && !global.frameskip;
|
||||
}
|
||||
|
||||
static bool stage_frame(void *arg) {
|
||||
StageFrameState *fstate = arg;
|
||||
StageInfo *stage = fstate->stage;
|
||||
|
||||
((global.replaymode == REPLAY_PLAY) ? replay_input : stage_input)();
|
||||
|
||||
if(global.game_over != GAMEOVER_TRANSITIONING) {
|
||||
if((!global.boss || boss_is_fleeing(global.boss)) && !global.dialog) {
|
||||
stage->procs->event();
|
||||
}
|
||||
|
||||
if(stage->type == STAGE_SPELL && !global.boss && global.game_over != GAMEOVER_RESTART) {
|
||||
stage_finish(GAMEOVER_WIN);
|
||||
fstate->transition_delay = 60;
|
||||
}
|
||||
}
|
||||
|
||||
if(!global.timer && stage->type != STAGE_SPELL) {
|
||||
// must be done here to let the event function start a BGM first
|
||||
display_stage_title(stage);
|
||||
}
|
||||
|
||||
replay_stage_check_desync(global.replay_stage, global.frames, (tsrand() ^ global.plr.points) & 0xFFFF, global.replaymode);
|
||||
stage_logic();
|
||||
|
||||
if(global.replaymode == REPLAY_RECORD && global.plr.points > progress.hiscore) {
|
||||
progress.hiscore = global.plr.points;
|
||||
}
|
||||
|
||||
if(fstate->transition_delay) {
|
||||
--fstate->transition_delay;
|
||||
}
|
||||
|
||||
if(global.frameskip && global.frames % global.frameskip) {
|
||||
if(!fstate->transition_delay) {
|
||||
update_transition();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fpscounter_update(&global.fps);
|
||||
|
||||
if(global.replaymode == REPLAY_RECORD) {
|
||||
uint16_t replay_fps = (uint16_t)rint(global.fps.fps);
|
||||
|
||||
if(replay_fps != fstate->last_replay_fps) {
|
||||
replay_stage_event(global.replay_stage, global.frames, EV_FPS, replay_fps);
|
||||
fstate->last_replay_fps = replay_fps;
|
||||
}
|
||||
}
|
||||
|
||||
tsrand_lock(&global.rand_game);
|
||||
tsrand_switch(&global.rand_visual);
|
||||
stage_draw_scene(stage);
|
||||
tsrand_unlock(&global.rand_game);
|
||||
tsrand_switch(&global.rand_game);
|
||||
|
||||
draw_transition();
|
||||
|
||||
if(!fstate->transition_delay) {
|
||||
update_transition();
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
|
||||
return global.game_over <= 0;
|
||||
}
|
||||
|
||||
void stage_loop(StageInfo *stage) {
|
||||
assert(stage);
|
||||
assert(stage->procs);
|
||||
|
@ -566,68 +643,9 @@ void stage_loop(StageInfo *stage) {
|
|||
|
||||
stage->procs->begin();
|
||||
|
||||
int transition_delay = 0;
|
||||
|
||||
while(global.game_over <= 0) {
|
||||
if(global.game_over != GAMEOVER_TRANSITIONING) {
|
||||
if((!global.boss || boss_is_fleeing(global.boss)) && !global.dialog) {
|
||||
stage->procs->event();
|
||||
}
|
||||
|
||||
if(stage->type == STAGE_SPELL && !global.boss && global.game_over != GAMEOVER_RESTART) {
|
||||
stage_finish(GAMEOVER_WIN);
|
||||
transition_delay = 60;
|
||||
}
|
||||
}
|
||||
|
||||
if(!global.timer && stage->type != STAGE_SPELL) {
|
||||
// must be done here to let the event function start a BGM first
|
||||
display_stage_title(stage);
|
||||
}
|
||||
|
||||
((global.replaymode == REPLAY_PLAY) ? replay_input : stage_input)();
|
||||
replay_stage_check_desync(global.replay_stage, global.frames, (tsrand() ^ global.plr.points) & 0xFFFF, global.replaymode);
|
||||
|
||||
stage_logic();
|
||||
|
||||
if(global.replaymode == REPLAY_RECORD && global.plr.points > progress.hiscore) {
|
||||
progress.hiscore = global.plr.points;
|
||||
}
|
||||
|
||||
if(transition_delay) {
|
||||
--transition_delay;
|
||||
}
|
||||
|
||||
if(global.frameskip && global.frames % global.frameskip) {
|
||||
if(!transition_delay) {
|
||||
update_transition();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(fpscounter_update(&global.fps) && global.replaymode == REPLAY_RECORD) {
|
||||
replay_stage_event(global.replay_stage, global.frames, EV_FPS, global.fps.show_fps);
|
||||
}
|
||||
|
||||
tsrand_lock(&global.rand_game);
|
||||
tsrand_switch(&global.rand_visual);
|
||||
stage_draw_scene(stage);
|
||||
tsrand_unlock(&global.rand_game);
|
||||
tsrand_switch(&global.rand_game);
|
||||
|
||||
draw_transition();
|
||||
if(!transition_delay) {
|
||||
update_transition();
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(video.window);
|
||||
|
||||
if(global.replaymode == REPLAY_PLAY && gamekeypressed(KEY_SKIP)) {
|
||||
global.lasttime = SDL_GetTicks();
|
||||
} else {
|
||||
limit_frame_rate(&global.lasttime);
|
||||
}
|
||||
}
|
||||
StageFrameState fstate = { .stage = stage };
|
||||
fpscounter_reset(&global.fps);
|
||||
loop_at_fps(stage_frame, stage_fpslimit_condition, &fstate, FPS);
|
||||
|
||||
if(global.game_over == GAMEOVER_RESTART && global.stage->type != STAGE_SPELL) {
|
||||
stop_bgm(true);
|
||||
|
|
|
@ -517,7 +517,12 @@ void stage_draw_hud(void) {
|
|||
|
||||
glPopMatrix();
|
||||
|
||||
snprintf(buf, sizeof(buf), "%i fps", global.fps.show_fps);
|
||||
#ifdef DEBUG
|
||||
snprintf(buf, sizeof(buf), "%.16f fps", global.fps.fps);
|
||||
#else
|
||||
snprintf(buf, sizeof(buf), "%.2f fps", global.fps.fps);
|
||||
#endif
|
||||
|
||||
draw_text(AL_Right, SCREEN_W, SCREEN_H - 0.5 * stringheight(buf, _fonts.standard), buf, _fonts.standard);
|
||||
|
||||
if(global.boss) {
|
||||
|
|
103
src/util.c
103
src/util.c
|
@ -302,41 +302,80 @@ float smoothreclamp(float x, float old_min, float old_max, float new_min, float
|
|||
// gl/video utils
|
||||
//
|
||||
|
||||
void limit_frame_rate(uint64_t *lasttime) {
|
||||
if(global.frameskip) {
|
||||
return;
|
||||
}
|
||||
|
||||
double passed = (double)(SDL_GetPerformanceCounter() - *lasttime) / SDL_GetPerformanceFrequency();
|
||||
double delay = max(0, 1.0 / FPS - passed);
|
||||
int32_t delay_ms = (int32_t)rint(delay * 1000.0);
|
||||
|
||||
if(delay_ms > 0) {
|
||||
SDL_Delay(delay_ms);
|
||||
}
|
||||
|
||||
*lasttime = SDL_GetPerformanceCounter();
|
||||
}
|
||||
|
||||
void fpscounter_reset(FPSCounter *fps) {
|
||||
fps->show_fps = FPS;
|
||||
fps->fps = 0;
|
||||
fps->fpstime = SDL_GetTicks();
|
||||
}
|
||||
long double frametime = 1.0 / FPS;
|
||||
const int log_size = sizeof(fps->frametimes)/sizeof(long double);
|
||||
|
||||
bool fpscounter_update(FPSCounter *fps) {
|
||||
bool updated = false;
|
||||
|
||||
if(SDL_GetTicks() > fps->fpstime+1000) {
|
||||
fps->show_fps = fps->fps;
|
||||
fps->fps = 0;
|
||||
fps->fpstime = SDL_GetTicks();
|
||||
updated = true;
|
||||
} else {
|
||||
fps->fps++;
|
||||
for(int i = 0; i < log_size; ++i) {
|
||||
fps->frametimes[i] = frametime;
|
||||
}
|
||||
|
||||
return updated;
|
||||
fps->fps = 1.0 / frametime;
|
||||
fps->last_update_time = time_get();
|
||||
}
|
||||
|
||||
void fpscounter_update(FPSCounter *fps) {
|
||||
const int log_size = sizeof(fps->frametimes)/sizeof(long double);
|
||||
double frametime = time_get() - fps->last_update_time;
|
||||
|
||||
memmove(fps->frametimes, fps->frametimes + 1, (log_size - 1) * sizeof(long double));
|
||||
fps->frametimes[log_size - 1] = frametime;
|
||||
|
||||
long double avg = 0.0;
|
||||
|
||||
for(int i = 0; i < log_size; ++i) {
|
||||
avg += fps->frametimes[i];
|
||||
}
|
||||
|
||||
fps->fps = 1.0 / (avg / log_size);
|
||||
fps->last_update_time = time_get();
|
||||
}
|
||||
|
||||
void loop_at_fps(bool (*frame_func)(void*), bool (*limiter_cond_func)(void*), void *arg, uint32_t fps) {
|
||||
assert(frame_func != NULL);
|
||||
assert(fps > 0);
|
||||
|
||||
long double real_time = time_get();
|
||||
long double next_frame_time = real_time;
|
||||
long double target_frame_time = ((long double)1.0) / fps;
|
||||
|
||||
int32_t delay = getenvint("TAISEI_FRAMELIMITER_SLEEP", 10);
|
||||
bool exact_delay = getenvint("TAISEI_FRAMELIMITER_SLEEP_EXACT", 1);
|
||||
|
||||
while(true) {
|
||||
real_time = time_get();
|
||||
|
||||
if(real_time < next_frame_time) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!frame_func(arg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!limiter_cond_func || limiter_cond_func(arg)) {
|
||||
next_frame_time = real_time + target_frame_time;
|
||||
|
||||
if(delay > 0) {
|
||||
int32_t realdelay = delay;
|
||||
int32_t mindelay = (int32_t)(1000 * (next_frame_time - time_get()));
|
||||
|
||||
if(realdelay > mindelay) {
|
||||
if(exact_delay) {
|
||||
log_debug("Delay of %i ignored. Minimum is %i but TAISEI_FRAMELIMITER_SLEEP_EXACT is active", realdelay, mindelay);
|
||||
realdelay = 0;
|
||||
} else {
|
||||
log_debug("Delay reduced from %i to %i", realdelay, mindelay);
|
||||
realdelay = mindelay;
|
||||
}
|
||||
}
|
||||
|
||||
if(realdelay > 0) {
|
||||
SDL_Delay(realdelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_ortho_ex(float w, float h) {
|
||||
|
@ -568,7 +607,7 @@ void _ts_assert_fail(const char *cond, const char *func, const char *file, int l
|
|||
int getenvint(const char *v, int defaultval) {
|
||||
char *e = getenv(v);
|
||||
|
||||
if(e) {
|
||||
if(e && *e) {
|
||||
return atoi(e);
|
||||
}
|
||||
|
||||
|
|
33
src/util.h
33
src/util.h
|
@ -15,21 +15,10 @@
|
|||
#include <zlib.h> // compiling under mingw may fail without this...
|
||||
#include <png.h>
|
||||
#include <SDL.h>
|
||||
#include "log.h"
|
||||
#include "hashtable.h"
|
||||
#include "vfs/public.h"
|
||||
|
||||
//
|
||||
// compatibility
|
||||
//
|
||||
|
||||
#ifndef __GNUC__ // clang defines this too
|
||||
#define __attribute__(...)
|
||||
#define __extension__
|
||||
#define PRAGMA(p)
|
||||
#else
|
||||
#define PRAGMA(p) _Pragma(#p)
|
||||
#endif
|
||||
#include "log.h"
|
||||
#include "compat.h"
|
||||
|
||||
//
|
||||
// string utils
|
||||
|
@ -48,7 +37,7 @@ bool strstartswith_any(const char *s, const char **earray) __attribute__((pure))
|
|||
void stralloc(char **dest, const char *src);
|
||||
char* strjoin(const char *first, ...) __attribute__((sentinel));
|
||||
char* vstrfmt(const char *fmt, va_list args);
|
||||
char* strfmt(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
char* strfmt(const char *fmt, ...) __attribute__((format(FORMAT_ATTR, 1, 2)));
|
||||
void strip_trailing_slashes(char *buf);
|
||||
char* strtok_r(char *str, const char *delim, char **nextp);
|
||||
char* strappend(char **dst, char *src);
|
||||
|
@ -101,14 +90,14 @@ float smoothreclamp(float x, float old_min, float old_max, float new_min, float
|
|||
//
|
||||
|
||||
typedef struct {
|
||||
int fpstime; // frame counter
|
||||
int fps;
|
||||
int show_fps;
|
||||
long double frametimes[120]; // size = number of frames to average
|
||||
double fps; // average fps over the last X frames
|
||||
long double last_update_time; // internal; last time the average was recalculated
|
||||
} FPSCounter;
|
||||
|
||||
void limit_frame_rate(uint64_t *lasttime);
|
||||
void loop_at_fps(bool (*frame_func)(void*), bool (*limiter_cond_func)(void*), void *arg, uint32_t fps);
|
||||
void fpscounter_reset(FPSCounter *fps);
|
||||
bool fpscounter_update(FPSCounter *fps);
|
||||
void fpscounter_update(FPSCounter *fps);
|
||||
void set_ortho(void);
|
||||
void set_ortho_ex(float w, float h);
|
||||
void colorfill(float r, float g, float b, float a);
|
||||
|
@ -129,10 +118,10 @@ void png_init_rwops_read(png_structp png, SDL_RWops *rwops);
|
|||
void png_init_rwops_write(png_structp png, SDL_RWops *rwops);
|
||||
|
||||
char* SDL_RWgets(SDL_RWops *rwops, char *buf, size_t bufsize);
|
||||
size_t SDL_RWprintf(SDL_RWops *rwops, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
size_t SDL_RWprintf(SDL_RWops *rwops, const char* fmt, ...) __attribute__((format(FORMAT_ATTR, 2, 3)));
|
||||
|
||||
// This is for the very few legitimate uses for printf/fprintf that shouldn't be replaced with log_*
|
||||
void tsfprintf(FILE *out, const char *restrict fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
void tsfprintf(FILE *out, const char *restrict fmt, ...) __attribute__((format(FORMAT_ATTR, 2, 3)));
|
||||
|
||||
//
|
||||
// misc utils
|
||||
|
@ -199,5 +188,3 @@ int sprintf(char *, const char*, ...) __attribute__((deprecated(
|
|||
"Use snprintf or strfmt instead")));
|
||||
|
||||
PRAGMA(GCC diagnostic pop)
|
||||
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ bool vfs_mount(VFSNode *root, const char *mountpoint, VFSNode *subtree);
|
|||
const char* vfs_iter(VFSNode *node, void **opaque);
|
||||
void vfs_iter_stop(VFSNode *node, void **opaque);
|
||||
|
||||
void vfs_set_error(char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void vfs_set_error(char *fmt, ...) __attribute__((format(FORMAT_ATTR, 1, 2)));
|
||||
void vfs_set_error_from_sdl(void);
|
||||
|
||||
void vfs_print_tree_recurse(SDL_RWops *dest, VFSNode *root, char *prefix, const char *name);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue