sub-millisecond but can be improved if we get rid of SDL timers.legacy
| @@ -7,7 +7,7 @@ libcommon_a_SOURCES = \ | |||||
| game.cpp game.h tiler.cpp tiler.h tileset.cpp tileset.h \ | game.cpp game.h tiler.cpp tiler.h tileset.cpp tileset.h \ | ||||
| scene.cpp scene.h font.cpp font.h layer.cpp layer.h map.cpp map.h \ | scene.cpp scene.h font.cpp font.h layer.cpp layer.h map.cpp map.h \ | ||||
| joystick.cpp joystick.h asset.cpp asset.h ticker.cpp ticker.h \ | joystick.cpp joystick.h asset.cpp asset.h ticker.cpp ticker.h \ | ||||
| forge.cpp forge.h video.cpp video.h \ | |||||
| forge.cpp forge.h video.cpp video.h timer.cpp timer.h \ | |||||
| debugfps.cpp debugfps.h | debugfps.cpp debugfps.h | ||||
| libcommon_a_CXXFLAGS = `pkg-config --cflags sdl gl SDL_image` | libcommon_a_CXXFLAGS = `pkg-config --cflags sdl gl SDL_image` | ||||
| @@ -65,7 +65,7 @@ void DebugFps::TickRender(float delta_time) | |||||
| char buf[1024]; | char buf[1024]; | ||||
| sprintf(buf, "%3.2f ms (%3.2f fps) -- max %3.2f ms -- #%i", | sprintf(buf, "%3.2f ms (%3.2f fps) -- max %3.2f ms -- #%i", | ||||
| mean, 1000.0f / mean, max, data->frame); | |||||
| 1000.0f * mean, 1.0f / mean, 1000.0f * max, data->frame); | |||||
| data->font->Print(10, 10, buf); | data->font->Print(10, 10, buf); | ||||
| data->font->Print(11, 10, buf); | data->font->Print(11, 10, buf); | ||||
| data->font->Print(10, 11, buf); | data->font->Print(10, 11, buf); | ||||
| @@ -21,9 +21,8 @@ | |||||
| static volatile int quit = 0; | static volatile int quit = 0; | ||||
| static GTimer *timer; | |||||
| static float delta_time; | |||||
| static int ticking = 0; | static int ticking = 0; | ||||
| static float const FPS = 30.0f; | |||||
| static gint main_quit(GtkWidget *widget, GdkEventExpose *event) | static gint main_quit(GtkWidget *widget, GdkEventExpose *event) | ||||
| { | { | ||||
| @@ -38,8 +37,6 @@ static gint main_quit(GtkWidget *widget, GdkEventExpose *event) | |||||
| static gboolean tick(void *widget) | static gboolean tick(void *widget) | ||||
| { | { | ||||
| // FIXME: do not do anything if the previous tick was too recent? | // FIXME: do not do anything if the previous tick was too recent? | ||||
| delta_time = 1000.0f * g_timer_elapsed(timer, NULL); | |||||
| g_timer_start(timer); | |||||
| // FIXME: only quit if all assets have been cleaned | // FIXME: only quit if all assets have been cleaned | ||||
| if (quit) | if (quit) | ||||
| @@ -48,7 +45,7 @@ static gboolean tick(void *widget) | |||||
| ticking = 1; | ticking = 1; | ||||
| /* Tick the game */ | /* Tick the game */ | ||||
| Ticker::TickGame(delta_time); | |||||
| Ticker::TickGame(); | |||||
| gtk_widget_draw(GTK_WIDGET(widget), NULL); | gtk_widget_draw(GTK_WIDGET(widget), NULL); | ||||
| @@ -82,8 +79,9 @@ static gint draw(GtkWidget *widget, GdkEventExpose *event) | |||||
| /* Clear the screen, tick the renderer, and show the frame */ | /* Clear the screen, tick the renderer, and show the frame */ | ||||
| Video::Clear(); | Video::Clear(); | ||||
| Ticker::TickRender(delta_time); | |||||
| Ticker::TickRender(); | |||||
| gtk_gl_area_swapbuffers(GTK_GL_AREA(widget)); | gtk_gl_area_swapbuffers(GTK_GL_AREA(widget)); | ||||
| Ticker::ClampFps(FPS); | |||||
| } | } | ||||
| return TRUE; | return TRUE; | ||||
| @@ -158,9 +156,8 @@ int main(int argc, char **argv) | |||||
| new DebugFps(); | new DebugFps(); | ||||
| //gtk_idle_add(tick, glarea); | //gtk_idle_add(tick, glarea); | ||||
| gtk_timeout_add(33, tick, glarea); | |||||
| gtk_timeout_add(1000 / FPS, tick, glarea); | |||||
| timer = g_timer_new(); | |||||
| gtk_main(); | gtk_main(); | ||||
| return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||||
| @@ -18,6 +18,8 @@ | |||||
| #include "ticker.h" | #include "ticker.h" | ||||
| #include "video.h" | #include "video.h" | ||||
| static int const FPS = 30.0f; | |||||
| int main(int argc, char **argv) | int main(int argc, char **argv) | ||||
| { | { | ||||
| /* Initialise SDL */ | /* Initialise SDL */ | ||||
| @@ -39,9 +41,6 @@ int main(int argc, char **argv) | |||||
| SDL_ShowCursor(0); | SDL_ShowCursor(0); | ||||
| SDL_WM_GrabInput(SDL_GRAB_ON); | SDL_WM_GrabInput(SDL_GRAB_ON); | ||||
| /* Initialise timer */ | |||||
| Uint32 ticks = SDL_GetTicks(); | |||||
| /* Initialise OpenGL */ | /* Initialise OpenGL */ | ||||
| Video::Setup(video->w, video->h); | Video::Setup(video->w, video->h); | ||||
| @@ -54,22 +53,16 @@ int main(int argc, char **argv) | |||||
| while (!game->Finished()) | while (!game->Finished()) | ||||
| { | { | ||||
| /* Compute delta time */ | |||||
| Uint32 newticks = SDL_GetTicks(); | |||||
| float delta_time = (float)(newticks - ticks); | |||||
| ticks = newticks; | |||||
| /* Tick the game */ | /* Tick the game */ | ||||
| Ticker::TickGame(delta_time); | |||||
| Ticker::TickGame(); | |||||
| /* Clear the screen, tick the renderer, and show the frame */ | /* Clear the screen, tick the renderer, and show the frame */ | ||||
| Video::Clear(); | Video::Clear(); | ||||
| Ticker::TickRender(delta_time); | |||||
| Ticker::TickRender(); | |||||
| SDL_GL_SwapBuffers(); | SDL_GL_SwapBuffers(); | ||||
| /* Clamp to desired framerate */ | /* Clamp to desired framerate */ | ||||
| while (SDL_GetTicks() < ticks + (33.33333f - 0.5f)) | |||||
| SDL_Delay(1); | |||||
| Ticker::ClampFps(FPS); | |||||
| } | } | ||||
| SDL_Quit(); | SDL_Quit(); | ||||
| @@ -13,6 +13,7 @@ | |||||
| #include "ticker.h" | #include "ticker.h" | ||||
| #include "asset.h" | #include "asset.h" | ||||
| #include "timer.h" | |||||
| /* | /* | ||||
| * Ticker implementation class | * Ticker implementation class | ||||
| @@ -29,6 +30,8 @@ public: | |||||
| { | { | ||||
| for (int i = 0; i < Asset::GROUP_COUNT; i++) | for (int i = 0; i < Asset::GROUP_COUNT; i++) | ||||
| list[i] = NULL; | list[i] = NULL; | ||||
| timer = new Timer(); | |||||
| bias = 0.0f; | |||||
| } | } | ||||
| ~TickerData() | ~TickerData() | ||||
| @@ -37,12 +40,19 @@ public: | |||||
| if (nassets) | if (nassets) | ||||
| fprintf(stderr, "ERROR: still %i assets in ticker\n", nassets); | fprintf(stderr, "ERROR: still %i assets in ticker\n", nassets); | ||||
| #endif | #endif | ||||
| delete timer; | |||||
| } | } | ||||
| private: | private: | ||||
| /* Asset management */ | |||||
| Asset *todo; | Asset *todo; | ||||
| Asset *list[Asset::GROUP_COUNT]; | Asset *list[Asset::GROUP_COUNT]; | ||||
| int nassets; | int nassets; | ||||
| /* FPS management */ | |||||
| float delta_time; | |||||
| Timer *timer; | |||||
| float bias; | |||||
| } | } | ||||
| tickerdata; | tickerdata; | ||||
| @@ -61,8 +71,11 @@ void Ticker::Register(Asset *asset) | |||||
| data->todo = asset; | data->todo = asset; | ||||
| } | } | ||||
| void Ticker::TickGame(float delta_time) | |||||
| void Ticker::TickGame() | |||||
| { | { | ||||
| data->delta_time = data->timer->GetSeconds(); | |||||
| data->bias += data->delta_time; | |||||
| /* Insert waiting objects in the appropriate lists */ | /* Insert waiting objects in the appropriate lists */ | ||||
| while (data->todo) | while (data->todo) | ||||
| { | { | ||||
| @@ -93,15 +106,23 @@ void Ticker::TickGame(float delta_time) | |||||
| for (int i = 0; i < Asset::GROUP_COUNT; i++) | for (int i = 0; i < Asset::GROUP_COUNT; i++) | ||||
| for (Asset *a = data->list[i]; a; a = a->next) | for (Asset *a = data->list[i]; a; a = a->next) | ||||
| if (!a->destroy) | if (!a->destroy) | ||||
| a->TickGame(delta_time); | |||||
| a->TickGame(data->delta_time); | |||||
| } | } | ||||
| void Ticker::TickRender(float delta_time) | |||||
| void Ticker::TickRender() | |||||
| { | { | ||||
| /* Tick objects for the render loop */ | /* Tick objects for the render loop */ | ||||
| for (int i = 0; i < Asset::GROUP_COUNT; i++) | for (int i = 0; i < Asset::GROUP_COUNT; i++) | ||||
| for (Asset *a = data->list[i]; a; a = a->next) | for (Asset *a = data->list[i]; a; a = a->next) | ||||
| if (!a->destroy) | if (!a->destroy) | ||||
| a->TickRender(delta_time); | |||||
| a->TickRender(data->delta_time); | |||||
| } | |||||
| void Ticker::ClampFps(float fps) | |||||
| { | |||||
| float ideal_time = 1.0f / fps; | |||||
| data->timer->WaitSeconds(ideal_time - data->bias); | |||||
| data->bias -= ideal_time; | |||||
| } | } | ||||
| @@ -21,8 +21,9 @@ class Ticker | |||||
| public: | public: | ||||
| static void Register(Asset *asset); | static void Register(Asset *asset); | ||||
| static void TickGame(float delta_time); | |||||
| static void TickRender(float delta_time); | |||||
| static void TickGame(); | |||||
| static void TickRender(); | |||||
| static void ClampFps(float fps); | |||||
| }; | }; | ||||
| #endif // __DH_TICKER_H__ | #endif // __DH_TICKER_H__ | ||||
| @@ -0,0 +1,113 @@ | |||||
| // | |||||
| // Deus Hax (working title) | |||||
| // Copyright (c) 2010 Sam Hocevar <sam@hocevar.net> | |||||
| // | |||||
| #if defined HAVE_CONFIG_H | |||||
| # include "config.h" | |||||
| #endif | |||||
| #include <cstdlib> | |||||
| #include <cstdio> | |||||
| #include <stdint.h> | |||||
| #if defined __linux__ | |||||
| # include <sys/time.h> | |||||
| #elif defined _WIN32 | |||||
| # define WIN32_LEAN_AND_MEAN | |||||
| # include <windows.h> | |||||
| #else | |||||
| # include <SDL.h> | |||||
| #endif | |||||
| // XXX | |||||
| #include <SDL.h> | |||||
| #include "timer.h" | |||||
| /* | |||||
| * Timer implementation class | |||||
| */ | |||||
| class TimerData | |||||
| { | |||||
| friend class Timer; | |||||
| private: | |||||
| TimerData() | |||||
| { | |||||
| #if defined __linux__ | |||||
| gettimeofday(&tv0, NULL); | |||||
| #elif defined _WIN32 | |||||
| LARGE_INTEGER tmp; | |||||
| QueryPerformanceFrequency(&tmp); | |||||
| seconds_per_cycle = 1.0f / tmp.QuadPart; | |||||
| QueryPerformanceCounter(&cycles0); | |||||
| #else | |||||
| SDL_Init(SDL_INIT_TIMER); | |||||
| ticks0 = SDL_GetTicks(); | |||||
| #endif | |||||
| } | |||||
| float GetSeconds(bool update) | |||||
| { | |||||
| float ret; | |||||
| #if defined __linux__ | |||||
| struct timeval tv; | |||||
| gettimeofday(&tv, NULL); | |||||
| ret = 1e-6f * (tv.tv_usec - tv0.tv_usec) + (tv.tv_sec - tv0.tv_sec); | |||||
| if (update) | |||||
| tv0 = tv; | |||||
| #elif defined _WIN32 | |||||
| LARGE_INTEGER cycles; | |||||
| QueryPerformanceCounter(&cycles); | |||||
| ret = seconds_per_cycle * (cycles.QuadPart - cycles0.QuadPart); | |||||
| if (update) | |||||
| cycles0 = cycles; | |||||
| #else | |||||
| Uint32 ticks = SDL_GetTicks(); | |||||
| ret = 1e-3f * (ticks - ticks0); | |||||
| if (update) | |||||
| ticks0 = ticks; | |||||
| #endif | |||||
| return ret; | |||||
| } | |||||
| #if defined __linux__ | |||||
| struct timeval tv0; | |||||
| #elif defined _WIN32 | |||||
| float seconds_per_cycle; | |||||
| LARGE_INTEGER cycles0; | |||||
| #else | |||||
| Uint32 ticks0; | |||||
| #endif | |||||
| }; | |||||
| /* | |||||
| * Timer public class | |||||
| */ | |||||
| Timer::Timer() | |||||
| { | |||||
| data = new TimerData(); | |||||
| } | |||||
| Timer::~Timer() | |||||
| { | |||||
| delete data; | |||||
| } | |||||
| float Timer::GetSeconds() | |||||
| { | |||||
| return data->GetSeconds(true); | |||||
| } | |||||
| void Timer::WaitSeconds(float seconds) | |||||
| { | |||||
| float sleep = seconds - data->GetSeconds(false); | |||||
| if (sleep <= 1e-4f) | |||||
| return; | |||||
| SDL_Delay((int)(sleep * 1000.0f + 0.5f)); | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| // | |||||
| // Deus Hax (working title) | |||||
| // Copyright (c) 2010 Sam Hocevar <sam@hocevar.net> | |||||
| // | |||||
| // | |||||
| // The Timer class | |||||
| // --------------- | |||||
| // | |||||
| #if !defined __DH_TIMER_H__ | |||||
| #define __DH_TIMER_H__ | |||||
| class TimerData; | |||||
| class Timer | |||||
| { | |||||
| public: | |||||
| Timer(); | |||||
| ~Timer(); | |||||
| float GetSeconds(); | |||||
| void WaitSeconds(float milliseconds); | |||||
| private: | |||||
| TimerData *data; | |||||
| }; | |||||
| #endif // __DH_TIMER_H__ | |||||