mirror of https://github.com/yshui/picom.git
vblank: add GLX_SGI_video_sync based scheduler
Present extension based scheduler doesn't work well on NVIDIA drivers. GLX_SGI_video_sync is less accurate, but is rumoured to work. See [kwin's usage](https://invent.kde.org/plasma/kwin/-/blob/master/src/ backends/x11/standalone/x11_standalone_sgivideosyncvsyncmonitor.cpp) Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
parent
db6ed75b60
commit
db9b808bfb
|
@ -59,7 +59,7 @@ endif
|
||||||
|
|
||||||
if get_option('opengl')
|
if get_option('opengl')
|
||||||
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
|
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
|
||||||
deps += [dependency('gl', required: true), dependency('egl', required: true)]
|
deps += [dependency('gl', required: true), dependency('egl', required: true), dependency('threads', required:true)]
|
||||||
srcs += [ 'opengl.c' ]
|
srcs += [ 'opengl.c' ]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
@ -1501,7 +1501,8 @@ static bool redirect_start(session_t *ps) {
|
||||||
ps->last_schedule_delay = 0;
|
ps->last_schedule_delay = 0;
|
||||||
render_statistics_reset(&ps->render_stats);
|
render_statistics_reset(&ps->render_stats);
|
||||||
ps->vblank_scheduler =
|
ps->vblank_scheduler =
|
||||||
vblank_scheduler_new(ps->loop, &ps->c, session_get_target_window(ps));
|
vblank_scheduler_new(ps->loop, &ps->c, session_get_target_window(ps),
|
||||||
|
VBLANK_SCHEDULER_PRESENT);
|
||||||
if (!ps->vblank_scheduler) {
|
if (!ps->vblank_scheduler) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
281
src/vblank.c
281
src/vblank.c
|
@ -2,12 +2,26 @@
|
||||||
|
|
||||||
#include <ev.h>
|
#include <ev.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xcb/xproto.h>
|
#include <xcb/xproto.h>
|
||||||
|
#include "config.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 "compiler.h"
|
||||||
#include "config.h"
|
|
||||||
#include "list.h" // for container_of
|
#include "list.h" // for container_of
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "vblank.h"
|
#include "vblank.h"
|
||||||
|
@ -48,6 +62,7 @@ struct present_vblank_scheduler {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct vblank_scheduler_ops {
|
struct vblank_scheduler_ops {
|
||||||
|
size_t size;
|
||||||
void (*init)(struct vblank_scheduler *self);
|
void (*init)(struct vblank_scheduler *self);
|
||||||
void (*deinit)(struct vblank_scheduler *self);
|
void (*deinit)(struct vblank_scheduler *self);
|
||||||
void (*schedule)(struct vblank_scheduler *self);
|
void (*schedule)(struct vblank_scheduler *self);
|
||||||
|
@ -57,6 +72,242 @@ struct vblank_scheduler_ops {
|
||||||
static void
|
static void
|
||||||
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event);
|
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 = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
|
||||||
|
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) {
|
static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
|
||||||
auto self = (struct present_vblank_scheduler *)base;
|
auto self = (struct present_vblank_scheduler *)base;
|
||||||
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
|
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
|
||||||
|
@ -170,11 +421,22 @@ static bool handle_present_events(struct vblank_scheduler *base) {
|
||||||
static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = {
|
static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = {
|
||||||
[VBLANK_SCHEDULER_PRESENT] =
|
[VBLANK_SCHEDULER_PRESENT] =
|
||||||
{
|
{
|
||||||
|
.size = sizeof(struct present_vblank_scheduler),
|
||||||
.init = present_vblank_scheduler_init,
|
.init = present_vblank_scheduler_init,
|
||||||
.deinit = present_vblank_scheduler_deinit,
|
.deinit = present_vblank_scheduler_deinit,
|
||||||
.schedule = present_vblank_scheduler_schedule,
|
.schedule = present_vblank_scheduler_schedule,
|
||||||
.handle_x_events = handle_present_events,
|
.handle_x_events = handle_present_events,
|
||||||
},
|
},
|
||||||
|
#ifdef CONFIG_OPENGL
|
||||||
|
[VBLANK_SCHEDULER_SGI_VIDEO_SYNC] =
|
||||||
|
{
|
||||||
|
.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) {
|
static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
|
||||||
|
@ -250,13 +512,22 @@ void vblank_scheduler_free(struct vblank_scheduler *self) {
|
||||||
free(self);
|
free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
struct vblank_scheduler *
|
||||||
xcb_window_t target_window) {
|
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
||||||
struct vblank_scheduler *self = calloc(1, sizeof(struct present_vblank_scheduler));
|
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->target_window = target_window;
|
||||||
self->c = c;
|
self->c = c;
|
||||||
self->loop = loop;
|
self->loop = loop;
|
||||||
vblank_scheduler_ops[VBLANK_SCHEDULER_PRESENT].init(self);
|
init_fn(self);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <ev.h>
|
#include <ev.h>
|
||||||
#include <xcb/xproto.h>
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "x.h"
|
#include "x.h"
|
||||||
|
|
||||||
/// An object that schedule vblank events.
|
/// An object that schedule vblank events.
|
||||||
|
@ -38,8 +39,9 @@ typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *ev
|
||||||
/// is not enough memory.
|
/// is not enough memory.
|
||||||
bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb,
|
bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb,
|
||||||
void *user_data);
|
void *user_data);
|
||||||
struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
struct vblank_scheduler *
|
||||||
xcb_window_t target_window);
|
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
||||||
|
xcb_window_t target_window, enum vblank_scheduler_type type);
|
||||||
void vblank_scheduler_free(struct vblank_scheduler *);
|
void vblank_scheduler_free(struct vblank_scheduler *);
|
||||||
|
|
||||||
bool vblank_handle_x_events(struct vblank_scheduler *self);
|
bool vblank_handle_x_events(struct vblank_scheduler *self);
|
||||||
|
|
Loading…
Reference in New Issue