Create a gist now

Instantly share code, notes, and snippets.

@Cloudef /glvsync.c
Last active Dec 2, 2016

What would you like to do?
Force glXSwapInterval* to whatever you want
/* 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