#include #include #include /* limit-fps version 1.1 * by Steven Elliott (aka xoltra) * * A small utility to limit the FPS of Cube even with the binary client * without recompiling the source. This may be beneficial to Linux users with * high end graphics cards who are getting FPS rates that greatly exceed the * refresh rate of their monitors (and are therefore wasted) and who are also * worried about the wear and tear on their systems, possibly because their * graphics cards are overheating. Windows' GL seems to limit the frame rate * to the refresh limit of the monitor, so there is no need for this utility * on Windows. * * The limit is enforced by overriding a function in a shared library that is * called once per frame (SDL_GL_SwapBuffers()). The amount of time * elapsed since the last call is compared to a minimum amount of time. If * that minimum is not reached a delay for the remaining amount of time is * added. * * Compile this file with: * gcc -O3 -shared -ldl limit-fps.c -o liblimit-fps.so * And then apply it with * LD_PRELOAD=/liblimit-fps.so cube * where "cube" in the above is whatever you run to start your Cube client. * alternatively apply this program to everything with * export LD_PRELOAD=/liblimit-fps.so * Or just add the above to whatever script you use to start your Cube client * so that it does not affect other things. Finally, make sure the "MAX_FPS" * environment variable is set * export MAX_FPS=100 * By trial and error find a value that causes the FPS rate to just barely * exceed the refresh rate of your monitor and which does not impede your * play at all. * * This utility may be useful for things other than Cube, but I have not tried * it. * * This script is subject to the same open source license as Cube (ZLIB like * license). See the "LICENSE" section in the cube_source/readme.txt file in * the Cube source for details. */ static void (* sdl_swap_real)(void); /* We have to mess with dynamically loading libSDL.so in order to deal with * the fact that we are trying to override a symbol, and then calling the true * symbol with the same name. */ static void load_sdl() { void *sdl_hand; sdl_hand = dlopen("libSDL-1.2.so.0", RTLD_LAZY | RTLD_GLOBAL); if (!sdl_hand) { fprintf(stderr, "Could not load libSDL: %s\n", dlerror()); exit(1); } sdl_swap_real = dlsym(sdl_hand, "SDL_GL_SwapBuffers"); if (!sdl_swap_real) { fprintf(stderr, "Could not load SDL_GL_SwapBuffers: %s\n", dlerror()); exit(1); } /* We can call the close here since libraries are loaded and unloaded based * on usage counts. So Cube, which is using it in this process, will keep * it loaded. */ dlclose(sdl_hand); } static void limit_fps() { static int min_ms = -1; static int max_fps = 0; static char *max_fps_str = NULL; static struct timeval last = {0}; static struct timeval now = {0}; struct timespec nano_delay; int elapsed_ms; int delay_ms; if (min_ms == -1) { max_fps_str = getenv("MAX_FPS"); if (max_fps_str) { max_fps = atoi(max_fps_str); min_ms = 1000 / max_fps; } else { min_ms = 0; } } if (min_ms) { gettimeofday(&now, NULL); if (last.tv_sec && last.tv_usec) { elapsed_ms = 1000 * (now.tv_sec - last.tv_sec) + (now.tv_usec - last.tv_usec) / 1000; if (elapsed_ms < min_ms) { delay_ms = min_ms - elapsed_ms; nano_delay.tv_sec = 0; nano_delay.tv_nsec = 1000000 * delay_ms; nanosleep(&nano_delay, 0); } } gettimeofday(&last, NULL); } } /* The following assumes that double buffering is being done with SDL. */ void SDL_GL_SwapBuffers() { static int inited = 0; if (!inited) { load_sdl(); inited = 1; } sdl_swap_real(); /* The real SDL_GL_SwapBuffers() in libSDL.so */ /* Limit FPS here. Ideally we would wait until an interesting event, * such as a mouse, keyboard, enet, etc. event. */ limit_fps(); }