|
|
|
|
@@ -2,12 +2,25 @@
|
|
|
|
|
|
|
|
|
|
#include <ev.h>
|
|
|
|
|
#include <inttypes.h>
|
|
|
|
|
#include <stdatomic.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <xcb/xcb.h>
|
|
|
|
|
#include <xcb/xproto.h>
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_OPENGL
|
|
|
|
|
// Enable sgi_video_sync_vblank_scheduler
|
|
|
|
|
#include <GL/glx.h>
|
|
|
|
|
#include <X11/X.h>
|
|
|
|
|
#include <X11/Xlib-xcb.h>
|
|
|
|
|
#include <X11/Xlib.h>
|
|
|
|
|
#include <X11/Xutil.h>
|
|
|
|
|
#include <pthread.h>
|
|
|
|
|
|
|
|
|
|
#include "backend/gl/glx.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include "compiler.h"
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#include "list.h" // for container_of
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "vblank.h"
|
|
|
|
|
@@ -48,6 +61,7 @@ struct present_vblank_scheduler {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct vblank_scheduler_ops {
|
|
|
|
|
size_t size;
|
|
|
|
|
void (*init)(struct vblank_scheduler *self);
|
|
|
|
|
void (*deinit)(struct vblank_scheduler *self);
|
|
|
|
|
void (*schedule)(struct vblank_scheduler *self);
|
|
|
|
|
@@ -57,6 +71,242 @@ struct vblank_scheduler_ops {
|
|
|
|
|
static void
|
|
|
|
|
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event);
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_OPENGL
|
|
|
|
|
struct sgi_video_sync_vblank_scheduler {
|
|
|
|
|
struct vblank_scheduler base;
|
|
|
|
|
|
|
|
|
|
// Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread.
|
|
|
|
|
// ... and all the thread shenanigans that come with it.
|
|
|
|
|
_Atomic unsigned int last_msc;
|
|
|
|
|
_Atomic uint64_t last_ust;
|
|
|
|
|
ev_async notify;
|
|
|
|
|
pthread_t sync_thread;
|
|
|
|
|
bool running, error;
|
|
|
|
|
|
|
|
|
|
/// Protects `running`, `error` and `base.vblank_event_requested`
|
|
|
|
|
pthread_mutex_t vblank_requested_mtx;
|
|
|
|
|
pthread_cond_t vblank_requested_cnd;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct sgi_video_sync_thread_args {
|
|
|
|
|
struct sgi_video_sync_vblank_scheduler *self;
|
|
|
|
|
int start_status;
|
|
|
|
|
pthread_mutex_t start_mtx;
|
|
|
|
|
pthread_cond_t start_cnd;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
|
|
|
|
|
const char *glx_ext = glXQueryExtensionsString(dpy, screen);
|
|
|
|
|
const char *needle = "GLX_SGI_video_sync";
|
|
|
|
|
char *found = strstr(glx_ext, needle);
|
|
|
|
|
if (!found) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (found != glx_ext && found[-1] != ' ') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (found[strlen(needle)] != ' ' && found[strlen(needle)] != '\0') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)(void *)glXGetProcAddress(
|
|
|
|
|
(const GLubyte *)"glXWaitVideoSyncSGI");
|
|
|
|
|
if (!glXWaitVideoSyncSGI) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void *sgi_video_sync_thread(void *data) {
|
|
|
|
|
auto args = (struct sgi_video_sync_thread_args *)data;
|
|
|
|
|
auto self = args->self;
|
|
|
|
|
Display *dpy = XOpenDisplay(NULL);
|
|
|
|
|
int error_code = 0;
|
|
|
|
|
if (!dpy) {
|
|
|
|
|
error_code = 1;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
Window root = DefaultRootWindow(dpy), dummy = None;
|
|
|
|
|
int screen = DefaultScreen(dpy);
|
|
|
|
|
int ncfg = 0;
|
|
|
|
|
GLXFBConfig *cfg_ = glXChooseFBConfig(
|
|
|
|
|
dpy, screen,
|
|
|
|
|
(int[]){GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 0},
|
|
|
|
|
&ncfg);
|
|
|
|
|
GLXContext ctx = NULL;
|
|
|
|
|
GLXDrawable drawable = None;
|
|
|
|
|
|
|
|
|
|
if (!cfg_) {
|
|
|
|
|
error_code = 2;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
GLXFBConfig cfg = cfg_[0];
|
|
|
|
|
XFree(cfg_);
|
|
|
|
|
|
|
|
|
|
XVisualInfo *vi = glXGetVisualFromFBConfig(dpy, cfg);
|
|
|
|
|
if (!vi) {
|
|
|
|
|
error_code = 3;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Visual *visual = vi->visual;
|
|
|
|
|
const int depth = vi->depth;
|
|
|
|
|
XFree(vi);
|
|
|
|
|
|
|
|
|
|
Colormap colormap = XCreateColormap(dpy, root, visual, AllocNone);
|
|
|
|
|
XSetWindowAttributes attributes;
|
|
|
|
|
attributes.colormap = colormap;
|
|
|
|
|
|
|
|
|
|
dummy = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, InputOutput, visual,
|
|
|
|
|
CWColormap, &attributes);
|
|
|
|
|
XFreeColormap(dpy, colormap);
|
|
|
|
|
if (dummy == None) {
|
|
|
|
|
error_code = 4;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drawable = glXCreateWindow(dpy, cfg, dummy, NULL);
|
|
|
|
|
if (drawable == None) {
|
|
|
|
|
error_code = 5;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx = glXCreateNewContext(dpy, cfg, GLX_RGBA_TYPE, 0, true);
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
|
error_code = 6;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!glXMakeContextCurrent(dpy, drawable, drawable, ctx)) {
|
|
|
|
|
error_code = 7;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!check_sgi_video_sync_extension(dpy, screen)) {
|
|
|
|
|
error_code = 8;
|
|
|
|
|
goto start_failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&args->start_mtx);
|
|
|
|
|
args->start_status = 0;
|
|
|
|
|
pthread_cond_signal(&args->start_cnd);
|
|
|
|
|
pthread_mutex_unlock(&args->start_mtx);
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
|
|
|
|
while (self->running) {
|
|
|
|
|
if (!self->base.vblank_event_requested) {
|
|
|
|
|
pthread_cond_wait(&self->vblank_requested_cnd,
|
|
|
|
|
&self->vblank_requested_mtx);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
|
|
|
|
|
|
|
|
|
unsigned int last_msc;
|
|
|
|
|
glXWaitVideoSyncSGI(1, 0, &last_msc);
|
|
|
|
|
|
|
|
|
|
struct timespec now;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
|
atomic_store(&self->last_msc, last_msc);
|
|
|
|
|
atomic_store(&self->last_ust,
|
|
|
|
|
(uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000));
|
|
|
|
|
ev_async_send(self->base.loop, &self->notify);
|
|
|
|
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
|
|
|
|
}
|
|
|
|
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
|
|
start_failed:
|
|
|
|
|
pthread_mutex_lock(&args->start_mtx);
|
|
|
|
|
args->start_status = error_code;
|
|
|
|
|
pthread_cond_signal(&args->start_cnd);
|
|
|
|
|
pthread_mutex_unlock(&args->start_mtx);
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
if (dpy) {
|
|
|
|
|
glXMakeCurrent(dpy, None, NULL);
|
|
|
|
|
if (ctx) {
|
|
|
|
|
glXDestroyContext(dpy, ctx);
|
|
|
|
|
}
|
|
|
|
|
if (drawable) {
|
|
|
|
|
glXDestroyWindow(dpy, drawable);
|
|
|
|
|
}
|
|
|
|
|
if (dummy) {
|
|
|
|
|
XDestroyWindow(dpy, dummy);
|
|
|
|
|
}
|
|
|
|
|
XCloseDisplay(dpy);
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
|
|
|
|
|
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
|
|
|
|
log_verbose("Requesting vblank event for msc %d", self->last_msc + 1);
|
|
|
|
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
|
|
|
|
assert(!base->vblank_event_requested);
|
|
|
|
|
base->vblank_event_requested = true;
|
|
|
|
|
pthread_cond_signal(&self->vblank_requested_cnd);
|
|
|
|
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
|
|
|
|
|
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
|
|
|
|
|
auto event = (struct vblank_event){
|
|
|
|
|
.msc = atomic_load(&sched->last_msc),
|
|
|
|
|
.ust = atomic_load(&sched->last_ust),
|
|
|
|
|
};
|
|
|
|
|
sched->base.vblank_event_requested = false;
|
|
|
|
|
log_verbose("Received vblank event for msc %lu", event.msc);
|
|
|
|
|
vblank_scheduler_invoke_callbacks(&sched->base, &event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
|
|
|
|
|
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
|
|
|
|
auto args = (struct sgi_video_sync_thread_args){
|
|
|
|
|
.self = self,
|
|
|
|
|
.start_status = -1,
|
|
|
|
|
};
|
|
|
|
|
pthread_mutex_init(&args.start_mtx, NULL);
|
|
|
|
|
pthread_cond_init(&args.start_cnd, NULL);
|
|
|
|
|
|
|
|
|
|
base->type = SGI_VIDEO_SYNC_VBLANK_SCHEDULER;
|
|
|
|
|
ev_async_init(&self->notify, sgi_video_sync_scheduler_callback);
|
|
|
|
|
ev_async_start(base->loop, &self->notify);
|
|
|
|
|
pthread_mutex_init(&self->vblank_requested_mtx, NULL);
|
|
|
|
|
pthread_cond_init(&self->vblank_requested_cnd, NULL);
|
|
|
|
|
|
|
|
|
|
self->running = true;
|
|
|
|
|
pthread_create(&self->sync_thread, NULL, sgi_video_sync_thread, &args);
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&args.start_mtx);
|
|
|
|
|
while (args.start_status == -1) {
|
|
|
|
|
pthread_cond_wait(&args.start_cnd, &args.start_mtx);
|
|
|
|
|
}
|
|
|
|
|
if (args.start_status != 0) {
|
|
|
|
|
log_fatal("Failed to start sgi_video_sync_thread, error code: %d",
|
|
|
|
|
args.start_status);
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
pthread_mutex_destroy(&args.start_mtx);
|
|
|
|
|
pthread_cond_destroy(&args.start_cnd);
|
|
|
|
|
log_info("Started sgi_video_sync_thread");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
|
|
|
|
|
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
|
|
|
|
ev_async_stop(base->loop, &self->notify);
|
|
|
|
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
|
|
|
|
self->running = false;
|
|
|
|
|
pthread_cond_signal(&self->vblank_requested_cnd);
|
|
|
|
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
|
|
|
|
|
|
|
|
|
pthread_join(self->sync_thread, NULL);
|
|
|
|
|
|
|
|
|
|
pthread_mutex_destroy(&self->vblank_requested_mtx);
|
|
|
|
|
pthread_cond_destroy(&self->vblank_requested_cnd);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
|
|
|
|
|
auto self = (struct present_vblank_scheduler *)base;
|
|
|
|
|
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
|
|
|
|
|
@@ -170,11 +420,22 @@ static bool handle_present_events(struct vblank_scheduler *base) {
|
|
|
|
|
static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = {
|
|
|
|
|
[PRESENT_VBLANK_SCHEDULER] =
|
|
|
|
|
{
|
|
|
|
|
.size = sizeof(struct present_vblank_scheduler),
|
|
|
|
|
.init = present_vblank_scheduler_init,
|
|
|
|
|
.deinit = present_vblank_scheduler_deinit,
|
|
|
|
|
.schedule = present_vblank_scheduler_schedule,
|
|
|
|
|
.handle_x_events = handle_present_events,
|
|
|
|
|
},
|
|
|
|
|
#ifdef CONFIG_OPENGL
|
|
|
|
|
[SGI_VIDEO_SYNC_VBLANK_SCHEDULER] =
|
|
|
|
|
{
|
|
|
|
|
.size = sizeof(struct sgi_video_sync_vblank_scheduler),
|
|
|
|
|
.init = sgi_video_sync_scheduler_init,
|
|
|
|
|
.deinit = sgi_video_sync_scheduler_deinit,
|
|
|
|
|
.schedule = sgi_video_sync_scheduler_schedule,
|
|
|
|
|
.handle_x_events = NULL,
|
|
|
|
|
},
|
|
|
|
|
#endif
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
|
|
|
|
|
@@ -239,13 +500,22 @@ void vblank_scheduler_free(struct vblank_scheduler *self) {
|
|
|
|
|
free(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
|
|
|
|
xcb_window_t target_window) {
|
|
|
|
|
struct vblank_scheduler *self = calloc(1, sizeof(struct present_vblank_scheduler));
|
|
|
|
|
struct vblank_scheduler *
|
|
|
|
|
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
|
|
|
|
xcb_window_t target_window, enum vblank_scheduler_type type) {
|
|
|
|
|
size_t object_size = vblank_scheduler_ops[type].size;
|
|
|
|
|
auto init_fn = vblank_scheduler_ops[type].init;
|
|
|
|
|
if (!object_size || !init_fn) {
|
|
|
|
|
log_error("Unsupported or invalid vblank scheduler type: %d", type);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(object_size >= sizeof(struct vblank_scheduler));
|
|
|
|
|
struct vblank_scheduler *self = calloc(1, object_size);
|
|
|
|
|
self->target_window = target_window;
|
|
|
|
|
self->c = c;
|
|
|
|
|
self->loop = loop;
|
|
|
|
|
vblank_scheduler_ops[PRESENT_VBLANK_SCHEDULER].init(self);
|
|
|
|
|
init_fn(self);
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|