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__ | |||||