Skip to content

Instantly share code, notes, and snippets.

@Cloudef
Last active November 23, 2022 10:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Cloudef/9103499 to your computer and use it in GitHub Desktop.
Save Cloudef/9103499 to your computer and use it in GitHub Desktop.
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));
}
@Cloudef
Copy link
Author

Cloudef commented Nov 23, 2022

If you are writing opengl app, you can just call glSwapInterval yourself. Though it seems what you want to actually do is use this https://developer.apple.com/documentation/quartzcore/cadisplaylink

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment