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));
}
@xuandu
Copy link

xuandu commented Nov 23, 2022

I don't know much about opengl on macos sorry. It sounds like you might need a opengl loader / wrangler library though (the system headers on windows at least are very bare bones, so this might be the case on osx too)

thank you for your apply ;
I originally wanted to control the video refresh rate through the swap interval of OpenGL to 30 or 60 FPS; Because the clock thread I wrote myself does not always update the 16.67MS YUV once; I need an accurate method to update video images, so I thought of using this; But it's a pity; If you have any other method to basically synchronize/correspond the video display rate with the screen refresh rate, please let me know. Thank you

@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