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 \ | |||
| 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 \ | |||
| forge.cpp forge.h video.cpp video.h \ | |||
| forge.cpp forge.h video.cpp video.h timer.cpp timer.h \ | |||
| debugfps.cpp debugfps.h | |||
| libcommon_a_CXXFLAGS = `pkg-config --cflags sdl gl SDL_image` | |||
| @@ -65,7 +65,7 @@ void DebugFps::TickRender(float delta_time) | |||
| char buf[1024]; | |||
| 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(11, 10, buf); | |||
| data->font->Print(10, 11, buf); | |||
| @@ -21,9 +21,8 @@ | |||
| static volatile int quit = 0; | |||
| static GTimer *timer; | |||
| static float delta_time; | |||
| static int ticking = 0; | |||
| static float const FPS = 30.0f; | |||
| 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) | |||
| { | |||
| // 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 | |||
| if (quit) | |||
| @@ -48,7 +45,7 @@ static gboolean tick(void *widget) | |||
| ticking = 1; | |||
| /* Tick the game */ | |||
| Ticker::TickGame(delta_time); | |||
| Ticker::TickGame(); | |||
| 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 */ | |||
| Video::Clear(); | |||
| Ticker::TickRender(delta_time); | |||
| Ticker::TickRender(); | |||
| gtk_gl_area_swapbuffers(GTK_GL_AREA(widget)); | |||
| Ticker::ClampFps(FPS); | |||
| } | |||
| return TRUE; | |||
| @@ -158,9 +156,8 @@ int main(int argc, char **argv) | |||
| new DebugFps(); | |||
| //gtk_idle_add(tick, glarea); | |||
| gtk_timeout_add(33, tick, glarea); | |||
| gtk_timeout_add(1000 / FPS, tick, glarea); | |||
| timer = g_timer_new(); | |||
| gtk_main(); | |||
| return EXIT_SUCCESS; | |||
| @@ -18,6 +18,8 @@ | |||
| #include "ticker.h" | |||
| #include "video.h" | |||
| static int const FPS = 30.0f; | |||
| int main(int argc, char **argv) | |||
| { | |||
| /* Initialise SDL */ | |||
| @@ -39,9 +41,6 @@ int main(int argc, char **argv) | |||
| SDL_ShowCursor(0); | |||
| SDL_WM_GrabInput(SDL_GRAB_ON); | |||
| /* Initialise timer */ | |||
| Uint32 ticks = SDL_GetTicks(); | |||
| /* Initialise OpenGL */ | |||
| Video::Setup(video->w, video->h); | |||
| @@ -54,22 +53,16 @@ int main(int argc, char **argv) | |||
| while (!game->Finished()) | |||
| { | |||
| /* Compute delta time */ | |||
| Uint32 newticks = SDL_GetTicks(); | |||
| float delta_time = (float)(newticks - ticks); | |||
| ticks = newticks; | |||
| /* Tick the game */ | |||
| Ticker::TickGame(delta_time); | |||
| Ticker::TickGame(); | |||
| /* Clear the screen, tick the renderer, and show the frame */ | |||
| Video::Clear(); | |||
| Ticker::TickRender(delta_time); | |||
| Ticker::TickRender(); | |||
| SDL_GL_SwapBuffers(); | |||
| /* Clamp to desired framerate */ | |||
| while (SDL_GetTicks() < ticks + (33.33333f - 0.5f)) | |||
| SDL_Delay(1); | |||
| Ticker::ClampFps(FPS); | |||
| } | |||
| SDL_Quit(); | |||
| @@ -13,6 +13,7 @@ | |||
| #include "ticker.h" | |||
| #include "asset.h" | |||
| #include "timer.h" | |||
| /* | |||
| * Ticker implementation class | |||
| @@ -29,6 +30,8 @@ public: | |||
| { | |||
| for (int i = 0; i < Asset::GROUP_COUNT; i++) | |||
| list[i] = NULL; | |||
| timer = new Timer(); | |||
| bias = 0.0f; | |||
| } | |||
| ~TickerData() | |||
| @@ -37,12 +40,19 @@ public: | |||
| if (nassets) | |||
| fprintf(stderr, "ERROR: still %i assets in ticker\n", nassets); | |||
| #endif | |||
| delete timer; | |||
| } | |||
| private: | |||
| /* Asset management */ | |||
| Asset *todo; | |||
| Asset *list[Asset::GROUP_COUNT]; | |||
| int nassets; | |||
| /* FPS management */ | |||
| float delta_time; | |||
| Timer *timer; | |||
| float bias; | |||
| } | |||
| tickerdata; | |||
| @@ -61,8 +71,11 @@ void Ticker::Register(Asset *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 */ | |||
| while (data->todo) | |||
| { | |||
| @@ -93,15 +106,23 @@ void Ticker::TickGame(float delta_time) | |||
| for (int i = 0; i < Asset::GROUP_COUNT; i++) | |||
| for (Asset *a = data->list[i]; a; a = a->next) | |||
| 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 */ | |||
| for (int i = 0; i < Asset::GROUP_COUNT; i++) | |||
| for (Asset *a = data->list[i]; a; a = a->next) | |||
| 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: | |||
| 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__ | |||
| @@ -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__ | |||