-
-
Save shuhaowu/878ebe2f4a2a7ee06d2d530627ccbdc1 to your computer and use it in GitHub Desktop.
Force glXSwapInterval* to whatever you want
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* gcc -std=c99 -fPIC -shared -Wl,-soname,glvsync.so glvsync.c -o glvsync.so | |
* gcc -m32 -std=c99 -fPIC -shared -Wl,-soname,glvsync.so glvsync.c -o glvsync.so (for 32bit) | |
* | |
* Force VSYNC interval on OpenGL applications | |
* Alternatively can also try FPS locking a OpenGL program | |
* Usage: LD_PRELOAD="/path/to/glvsync.so" ./program | |
*/ | |
#define _GNU_SOURCE | |
#include <dlfcn.h> | |
#include <GL/glx.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <stdbool.h> | |
#include <stdint.h> | |
#include <unistd.h> | |
#include <sched.h> | |
#include <time.h> | |
#include <err.h> | |
static uint32_t LOCK_FPS = 0; // Tries to lock swaps to FPS, 0 to disable (default) (override with _GLVSYNC_FPS) | |
// If using fps locking, it may be good idea to disable vsync | |
static int SYNC_INTERVAL = 1; // 60 FPS @ 60Hz (override with _GLVSYNC_INTERVAL env variable) | |
// e.g. 2 would be 30 FPS @ 60Hz, 0 to force disable vsync | |
extern void *_dl_sym(void*, const char*, void*); | |
static void* (*_dlsym)(void*, const char*) = NULL; | |
static int (*_glXSwapIntervalEXT)(Display*, GLXDrawable, int interval) = NULL; | |
static int (*_glXSwapIntervalSGI)(unsigned int interval) = NULL; | |
static int (*_glXSwapIntervalMESA)(unsigned int interval) = NULL; | |
static void (*_glXSwapBuffers)(Display*, GLXDrawable) = NULL; | |
static void* (*_glXGetProcAddress)(const GLubyte*) = NULL; | |
static void* (*_glXGetProcAddressARB)(const GLubyte*) = NULL; | |
static void* (*_glXGetProcAddressEXT)(const GLubyte*) = NULL; | |
static void* store_real_symbol_and_return_fake_symbol(const char *symbol, void *ret); | |
#define HOOK_DLSYM(x, y) if (!x) { if ((x = _dl_sym(RTLD_NEXT, __func__, y))) warnx("glvsync: HOOK dlsym"); } | |
#define HOOK(x) if (!x) if ((x = _dlsym(RTLD_NEXT, __func__))) { warnx("glvsync: HOOK %s", __func__); } | |
#define SET_IF_NOT_HOOKED(x, y) if (!x) { x = y; warnx("glvsync: SET %s", #x); } | |
#define WARN_ONCE(x, ...) do { static bool o = false; if (!o) { warnx(x, ##__VA_ARGS__); o = true; } } while (0) | |
static uint32_t get_lock_fps(void) | |
{ | |
static const char *val; | |
if (!val && (val = getenv("_GLVSYNC_FPS"))) | |
LOCK_FPS = strtol(val, NULL, 10); | |
return LOCK_FPS; | |
} | |
static int get_interval(void) | |
{ | |
static const char *val; | |
if (!val && (val = getenv("_GLVSYNC_INTERVAL"))) | |
SYNC_INTERVAL = strtol(val, NULL, 10); | |
return SYNC_INTERVAL; | |
} | |
int glXSwapIntervalEXT(Display *dpy, GLXDrawable drawable, int interval) | |
{ | |
(void)interval; | |
HOOK(_glXSwapIntervalEXT); | |
if (_glXSwapIntervalEXT) WARN_ONCE("glvsync: FORCE SWAP (EXT)"); | |
return (_glXSwapIntervalEXT ? _glXSwapIntervalEXT(dpy, drawable, get_interval()) : 0); | |
} | |
int glXSwapIntervalSGI(unsigned int interval) | |
{ | |
(void)interval; | |
HOOK(_glXSwapIntervalSGI); | |
if (_glXSwapIntervalSGI) WARN_ONCE("glvsync: FORCE SWAP (SGI)"); | |
return (_glXSwapIntervalSGI ? _glXSwapIntervalSGI(get_interval()) : 0); | |
} | |
int glXSwapIntervalMESA(unsigned int interval) | |
{ | |
(void)interval; | |
HOOK(_glXSwapIntervalMESA); | |
if (_glXSwapIntervalMESA) WARN_ONCE("glvsync: FORCE SWAP (MESA)"); | |
return (_glXSwapIntervalMESA ? _glXSwapIntervalMESA(get_interval()) : 0); | |
} | |
__GLXextFuncPtr glXGetProcAddressEXT(const GLubyte *procname) | |
{ | |
HOOK(_glXGetProcAddressEXT); | |
return (_glXGetProcAddressEXT ? store_real_symbol_and_return_fake_symbol(procname, _glXGetProcAddressEXT(procname)) : NULL); | |
} | |
__GLXextFuncPtr glXGetProcAddressARB(const GLubyte *procname) | |
{ | |
HOOK(_glXGetProcAddressARB); | |
return (_glXGetProcAddressARB ? store_real_symbol_and_return_fake_symbol(procname, _glXGetProcAddressARB(procname)) : NULL); | |
} | |
__GLXextFuncPtr glXGetProcAddress(const GLubyte *procname) | |
{ | |
HOOK(_glXGetProcAddress); | |
return (_glXGetProcAddress ? store_real_symbol_and_return_fake_symbol(procname, _glXGetProcAddress(procname)) : NULL); | |
} | |
static uint64_t get_time_ns(void) | |
{ | |
struct timespec ts; | |
clock_gettime(CLOCK_MONOTONIC, &ts); | |
return (uint64_t)ts.tv_sec * (uint64_t)1e9 + (uint64_t)ts.tv_nsec; | |
} | |
void glXSwapBuffers(Display *dpy, GLXDrawable drawable) | |
{ | |
HOOK(_glXSwapBuffers); | |
{ | |
static bool once = false; | |
if (!once) { | |
warnx("glvsync: INITIAL SWAP"); | |
glXSwapIntervalEXT(dpy, drawable, 0); | |
glXSwapIntervalSGI(0); | |
glXSwapIntervalMESA(0); | |
once = true; | |
} | |
} | |
_glXSwapBuffers(dpy, drawable); | |
if (get_lock_fps() > 0) { | |
static uint64_t last_time; | |
const double interval = 1e9 / get_lock_fps(); | |
const useconds_t step = interval / 1e6; | |
while (last_time > 0 && get_time_ns() - last_time < interval) { | |
sched_yield(); | |
usleep(step); | |
} | |
last_time = get_time_ns(); | |
} | |
} | |
void* store_real_symbol_and_return_fake_symbol(const char *symbol, void *ret) | |
{ | |
if (!ret || !symbol) | |
return ret; | |
if (!strcmp(symbol, "glXSwapIntervalEXT")) { | |
SET_IF_NOT_HOOKED(_glXSwapIntervalEXT, ret); | |
return glXSwapIntervalEXT; | |
} else if (!strcmp(symbol, "glXSwapIntervalSGI")) { | |
SET_IF_NOT_HOOKED(_glXSwapIntervalSGI, ret); | |
return glXSwapIntervalSGI; | |
} else if (!strcmp(symbol, "glXSwapIntervalMESA")) { | |
SET_IF_NOT_HOOKED(_glXSwapIntervalMESA, ret); | |
return glXSwapIntervalMESA; | |
} else if (!strcmp(symbol, "glXGetProcAddressEXT")) { | |
SET_IF_NOT_HOOKED(_glXGetProcAddressEXT, ret); | |
return glXGetProcAddressEXT; | |
} else if (!strcmp(symbol, "glXGetProcAddressARB")) { | |
SET_IF_NOT_HOOKED(_glXGetProcAddressARB, ret); | |
return glXGetProcAddressARB; | |
} else if (!strcmp(symbol, "glXGetProcAddress")) { | |
SET_IF_NOT_HOOKED(_glXGetProcAddress, ret); | |
return glXGetProcAddress; | |
} else if (!strcmp(symbol, "glXSwapBuffers")) { | |
SET_IF_NOT_HOOKED(_glXSwapBuffers, ret); | |
return glXSwapBuffers; | |
} | |
return ret; | |
} | |
void* dlsym(void *handle, const char *symbol) | |
{ | |
HOOK_DLSYM(_dlsym, dlsym); | |
if (symbol && !strcmp(symbol, "dlsym")) | |
return dlsym; | |
return store_real_symbol_and_return_fake_symbol(symbol, _dlsym(handle, symbol)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment