mirror of
https://github.com/mpv-player/mpv
synced 2025-01-10 00:49:32 +00:00
f4ce3b8bb9
I misunderstood how this extension works. If I understand it correctly now, it's worse than I thought. They key thing is that the (ust, msc, sbc) tripple is not for a single swap event. Instead, (ust, msc) run independently from sbc. Assuming a CFR display/compositor, this means you can at best know the vsync phase and frequency, but not the exact time a sbc changed value. There is GLX_INTEL_swap_event, which might work as expected, but it has no EGL equivalent (while GLX_OML_sync_control does, in theory). Redo the context_glx sync code. Now it's either more correct or less correct. I wanted to add proper skip detection (if a vsync gets skipped due to rendering taking too long and other problems), but it turned out to be too complex, so only some unused fields in vo.h are left of it. The "generic" skip detection has to do. The vsync_duration field is also unused by vo.c. Actually this seems to be an improvement. In cases where the flip call timing is off, but the real driver-level timing apparently still works, this will not report vsync skips or higher vsync jitter anymore. I could observe this with screenshots and fullscreen switching. On the other hand, maybe it just introduces an A/V offset or so. Why the fuck can't there be a proper API for retrieving these statistics? I'm not even asking for much.
506 lines
16 KiB
C
506 lines
16 KiB
C
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* mpv is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <GL/glx.h>
|
|
|
|
// FreeBSD 10.0-CURRENT lacks the GLX_ARB_create_context extension completely
|
|
#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB
|
|
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
|
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
|
|
#define GLX_CONTEXT_FLAGS_ARB 0x2094
|
|
#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
|
|
#ifndef __APPLE__
|
|
// These are respectively 0x00000001 and 0x00000002 on OSX
|
|
#define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001
|
|
#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
|
|
#endif
|
|
#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
|
|
#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
|
|
#endif
|
|
// GLX_EXT_create_context_es2_profile
|
|
#ifndef GLX_CONTEXT_ES2_PROFILE_BIT_EXT
|
|
#define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004
|
|
#endif
|
|
|
|
#include "osdep/timer.h"
|
|
#include "video/out/x11_common.h"
|
|
#include "context.h"
|
|
#include "utils.h"
|
|
|
|
struct priv {
|
|
GL gl;
|
|
XVisualInfo *vinfo;
|
|
GLXContext context;
|
|
GLXFBConfig fbc;
|
|
|
|
Bool (*XGetSyncValues)(Display*, GLXDrawable, int64_t*, int64_t*, int64_t*);
|
|
int64_t last_ust;
|
|
int64_t last_msc;
|
|
int64_t last_sbc;
|
|
int64_t last_sbc_mp_time;
|
|
int64_t user_sbc;
|
|
int64_t vsync_duration;
|
|
int64_t last_skipped_vsyncs;
|
|
int64_t last_queue_display_time;
|
|
};
|
|
|
|
static void glx_uninit(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
ra_gl_ctx_uninit(ctx);
|
|
|
|
if (p->vinfo)
|
|
XFree(p->vinfo);
|
|
if (p->context) {
|
|
Display *display = ctx->vo->x11->display;
|
|
glXMakeCurrent(display, None, NULL);
|
|
glXDestroyContext(display, p->context);
|
|
}
|
|
|
|
vo_x11_uninit(ctx->vo);
|
|
}
|
|
|
|
static bool create_context_x11_old(struct ra_ctx *ctx, GL *gl)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
Display *display = ctx->vo->x11->display;
|
|
struct vo *vo = ctx->vo;
|
|
|
|
if (p->context)
|
|
return true;
|
|
|
|
if (!p->vinfo) {
|
|
MP_FATAL(vo, "Can't create a legacy GLX context without X visual\n");
|
|
return false;
|
|
}
|
|
|
|
GLXContext new_context = glXCreateContext(display, p->vinfo, NULL, True);
|
|
if (!new_context) {
|
|
MP_FATAL(vo, "Could not create GLX context!\n");
|
|
return false;
|
|
}
|
|
|
|
if (!glXMakeCurrent(display, ctx->vo->x11->window, new_context)) {
|
|
MP_FATAL(vo, "Could not set GLX context!\n");
|
|
glXDestroyContext(display, new_context);
|
|
return false;
|
|
}
|
|
|
|
const char *glxstr = glXQueryExtensionsString(display, ctx->vo->x11->screen);
|
|
|
|
mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
|
|
|
|
p->context = new_context;
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef GLXContext (*glXCreateContextAttribsARBProc)
|
|
(Display*, GLXFBConfig, GLXContext, Bool, const int*);
|
|
|
|
static bool create_context_x11_gl3(struct ra_ctx *ctx, GL *gl, int gl_version,
|
|
bool es)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
struct vo *vo = ctx->vo;
|
|
|
|
if (p->context)
|
|
return true;
|
|
|
|
if (!ra_gl_ctx_test_version(ctx, gl_version, es))
|
|
return false;
|
|
|
|
glXCreateContextAttribsARBProc glXCreateContextAttribsARB =
|
|
(glXCreateContextAttribsARBProc)
|
|
glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
|
|
|
|
const char *glxstr =
|
|
glXQueryExtensionsString(vo->x11->display, vo->x11->screen);
|
|
bool have_ctx_ext = glxstr && !!strstr(glxstr, "GLX_ARB_create_context");
|
|
|
|
if (!(have_ctx_ext && glXCreateContextAttribsARB)) {
|
|
return false;
|
|
}
|
|
|
|
int ctx_flags = ctx->opts.debug ? GLX_CONTEXT_DEBUG_BIT_ARB : 0;
|
|
int profile_mask = GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
|
|
|
|
if (es) {
|
|
profile_mask = GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
|
|
if (!(glxstr && strstr(glxstr, "GLX_EXT_create_context_es2_profile")))
|
|
return false;
|
|
}
|
|
|
|
int context_attribs[] = {
|
|
GLX_CONTEXT_MAJOR_VERSION_ARB, MPGL_VER_GET_MAJOR(gl_version),
|
|
GLX_CONTEXT_MINOR_VERSION_ARB, MPGL_VER_GET_MINOR(gl_version),
|
|
GLX_CONTEXT_PROFILE_MASK_ARB, profile_mask,
|
|
GLX_CONTEXT_FLAGS_ARB, ctx_flags,
|
|
None
|
|
};
|
|
vo_x11_silence_xlib(1);
|
|
GLXContext context = glXCreateContextAttribsARB(vo->x11->display,
|
|
p->fbc, 0, True,
|
|
context_attribs);
|
|
vo_x11_silence_xlib(-1);
|
|
if (!context)
|
|
return false;
|
|
|
|
// set context
|
|
if (!glXMakeCurrent(vo->x11->display, vo->x11->window, context)) {
|
|
MP_FATAL(vo, "Could not set GLX context!\n");
|
|
glXDestroyContext(vo->x11->display, context);
|
|
return false;
|
|
}
|
|
|
|
p->context = context;
|
|
|
|
mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
|
|
|
|
if (gl_check_extension(glxstr, "GLX_OML_sync_control")) {
|
|
p->XGetSyncValues =
|
|
(void *)glXGetProcAddressARB((const GLubyte *)"glXGetSyncValuesOML");
|
|
}
|
|
if (p->XGetSyncValues)
|
|
MP_VERBOSE(vo, "Using GLX_OML_sync_control.\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
// The GL3/FBC initialization code roughly follows/copies from:
|
|
// http://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX)
|
|
// but also uses some of the old code.
|
|
|
|
static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs, bool alpha)
|
|
{
|
|
int fbcount;
|
|
GLXFBConfig *fbc = glXChooseFBConfig(vo->x11->display, vo->x11->screen,
|
|
attribs, &fbcount);
|
|
if (!fbc)
|
|
return NULL;
|
|
|
|
// The list in fbc is sorted (so that the first element is the best).
|
|
GLXFBConfig fbconfig = fbcount > 0 ? fbc[0] : NULL;
|
|
|
|
if (alpha) {
|
|
for (int n = 0; n < fbcount; n++) {
|
|
XVisualInfo *v = glXGetVisualFromFBConfig(vo->x11->display, fbc[n]);
|
|
if (v) {
|
|
bool is_rgba = vo_x11_is_rgba_visual(v);
|
|
XFree(v);
|
|
if (is_rgba) {
|
|
fbconfig = fbc[n];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XFree(fbc);
|
|
|
|
return fbconfig;
|
|
}
|
|
|
|
static void set_glx_attrib(int *attribs, int name, int value)
|
|
{
|
|
for (int n = 0; attribs[n * 2 + 0] != None; n++) {
|
|
if (attribs[n * 2 + 0] == name) {
|
|
attribs[n * 2 + 1] = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_vsync_oml(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
|
|
assert(p->XGetSyncValues);
|
|
|
|
p->last_skipped_vsyncs = 0;
|
|
p->user_sbc += 1;
|
|
|
|
// This extension returns two unrelated values:
|
|
// (ust, msc): clock time and incrementing counter of last vsync (this is
|
|
// reported continuously, even if we don't swap)
|
|
// sbc: swap counter of frame that was last displayed (every swap
|
|
// increments the user_sbc, and the reported sbc is the sbc
|
|
// of the frame that was just displayed)
|
|
// Invariants:
|
|
// - ust and msc change in lockstep (no value can change with the other)
|
|
// - msc is incremented; if you query it in a loop, and your thread isn't
|
|
// frozen or starved by the scheduler, it will usually not change or
|
|
// be incremented by 1 (while the ust will be incremented by vsync
|
|
// duration)
|
|
// - sbc is never higher than the user_sbc
|
|
// - (ust, msc) are equal to or higher by vsync increments than the display
|
|
// time of the frame referenced by the sbc
|
|
// Note that (ust, msc) and sbc are not locked to each other. The following
|
|
// can easily happen if vsync skips occur:
|
|
// - you draw a frame, in the meantime hardware swaps sbc_1
|
|
// - another display vsync happens during drawing
|
|
// - you call swap()
|
|
// - query (ust, msc) and sbc
|
|
// - sbc contains sbc_1, but (ust, msc) contains the vsync after it
|
|
// As a consequence, it's hard to detect the latency or vsync skips.
|
|
int64_t ust, msc, sbc;
|
|
if (!p->XGetSyncValues(ctx->vo->x11->display, ctx->vo->x11->window,
|
|
&ust, &msc, &sbc))
|
|
{
|
|
// Assume the extension is effectively unsupported.
|
|
p->vsync_duration = -1;
|
|
p->last_skipped_vsyncs = -1;
|
|
p->last_queue_display_time = -1;
|
|
return;
|
|
}
|
|
|
|
int64_t ust_passed = p->last_ust ? ust - p->last_ust : 0;
|
|
p->last_ust = ust;
|
|
|
|
int64_t msc_passed = p->last_msc ? msc - p->last_msc : 0;
|
|
p->last_msc = msc;
|
|
|
|
int64_t sbc_passed = sbc - p->last_sbc;
|
|
p->last_sbc = sbc;
|
|
|
|
// Display frame duration. This makes assumptions about UST (see below).
|
|
if (msc_passed && ust_passed)
|
|
p->vsync_duration = ust_passed / msc_passed;
|
|
|
|
// Only if a new frame was displayed (sbc increased) we have sort-of a
|
|
// chance that the current (ust, msc) is for the sbc. But this is racy,
|
|
// because skipped frames drops could have increased the msc right after the
|
|
// display event and before we queried the values. This code hopes for the
|
|
// best and ignores this.
|
|
if (sbc_passed) {
|
|
// The extension spec doesn't define what the UST is (not even its unit).
|
|
// Simply assume UST is a simple CLOCK_MONOTONIC usec value. The swap
|
|
// buffer call happened "some" but very small time ago, so we can get
|
|
// away with querying the current time. There is also the implicit
|
|
// assumption that mpv's timer and the UST use the same clock (which it
|
|
// does on POSIX).
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts))
|
|
return;
|
|
uint64_t now_monotonic = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
|
|
uint64_t ust_mp_time = mp_time_us() - (now_monotonic - ust);
|
|
|
|
// Assume this is exactly when the actual display event for this sbc
|
|
// happened. This is subject to the race mentioned above.
|
|
p->last_sbc_mp_time = ust_mp_time;
|
|
}
|
|
|
|
// At least one frame needs to be actually displayed before
|
|
// p->last_sbc_mp_time is set.
|
|
if (!sbc)
|
|
return;
|
|
|
|
// Extrapolate from the last sbc time (when a frame was actually displayed),
|
|
// and by adding the number of frames that were queued since to it.
|
|
// For every unit the sbc is smaller than user_sbc, the actual display
|
|
// is one frame ahead (assumes glx_swap_buffers() is called for every
|
|
// vsync).
|
|
p->last_queue_display_time =
|
|
p->last_sbc_mp_time + (p->user_sbc - sbc) * p->vsync_duration;
|
|
}
|
|
|
|
static void glx_swap_buffers(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
|
|
glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
|
|
|
|
if (p->XGetSyncValues)
|
|
update_vsync_oml(ctx);
|
|
}
|
|
|
|
static void glx_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
info->vsync_duration = p->vsync_duration;
|
|
info->skipped_vsyncs = p->last_skipped_vsyncs;
|
|
info->last_queue_display_time = p->last_queue_display_time;
|
|
}
|
|
|
|
static bool glx_init(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
|
|
struct vo *vo = ctx->vo;
|
|
GL *gl = &p->gl;
|
|
|
|
if (!vo_x11_init(ctx->vo))
|
|
goto uninit;
|
|
|
|
int glx_major, glx_minor;
|
|
|
|
if (!glXQueryVersion(vo->x11->display, &glx_major, &glx_minor)) {
|
|
MP_ERR(ctx, "GLX not found.\n");
|
|
goto uninit;
|
|
}
|
|
// FBConfigs were added in GLX version 1.3.
|
|
if (MPGL_VER(glx_major, glx_minor) < MPGL_VER(1, 3)) {
|
|
MP_ERR(ctx, "GLX version older than 1.3.\n");
|
|
goto uninit;
|
|
}
|
|
|
|
int glx_attribs[] = {
|
|
GLX_X_RENDERABLE, True,
|
|
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
|
|
GLX_RED_SIZE, 1,
|
|
GLX_GREEN_SIZE, 1,
|
|
GLX_BLUE_SIZE, 1,
|
|
GLX_ALPHA_SIZE, 0,
|
|
GLX_DOUBLEBUFFER, True,
|
|
None
|
|
};
|
|
GLXFBConfig fbc = NULL;
|
|
if (ctx->opts.want_alpha) {
|
|
set_glx_attrib(glx_attribs, GLX_ALPHA_SIZE, 1);
|
|
fbc = select_fb_config(vo, glx_attribs, true);
|
|
if (!fbc)
|
|
set_glx_attrib(glx_attribs, GLX_ALPHA_SIZE, 0);
|
|
}
|
|
if (!fbc)
|
|
fbc = select_fb_config(vo, glx_attribs, false);
|
|
if (!fbc) {
|
|
MP_ERR(ctx, "no GLX support present\n");
|
|
goto uninit;
|
|
}
|
|
|
|
int fbid = -1;
|
|
if (!glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_FBCONFIG_ID, &fbid))
|
|
MP_VERBOSE(ctx, "GLX chose FB config with ID 0x%x\n", fbid);
|
|
|
|
p->fbc = fbc;
|
|
p->vinfo = glXGetVisualFromFBConfig(vo->x11->display, fbc);
|
|
if (p->vinfo) {
|
|
MP_VERBOSE(ctx, "GLX chose visual with ID 0x%x\n",
|
|
(int)p->vinfo->visualid);
|
|
} else {
|
|
MP_WARN(ctx, "Selected GLX FB config has no associated X visual\n");
|
|
}
|
|
|
|
if (!vo_x11_create_vo_window(vo, p->vinfo, "gl"))
|
|
goto uninit;
|
|
|
|
bool success = false;
|
|
for (int n = 0; mpgl_preferred_gl_versions[n]; n++) {
|
|
int version = mpgl_preferred_gl_versions[n];
|
|
MP_VERBOSE(ctx, "Creating OpenGL %d.%d context...\n",
|
|
MPGL_VER_P(version));
|
|
if (version >= 300) {
|
|
success = create_context_x11_gl3(ctx, gl, version, false);
|
|
} else {
|
|
success = create_context_x11_old(ctx, gl);
|
|
}
|
|
if (success)
|
|
break;
|
|
}
|
|
if (!success) // try again for GLES
|
|
success = create_context_x11_gl3(ctx, gl, 200, true);
|
|
if (success && !glXIsDirect(vo->x11->display, p->context))
|
|
gl->mpgl_caps |= MPGL_CAP_SW;
|
|
if (!success)
|
|
goto uninit;
|
|
|
|
struct ra_gl_ctx_params params = {
|
|
.swap_buffers = glx_swap_buffers,
|
|
.get_vsync = glx_get_vsync,
|
|
};
|
|
|
|
if (!ra_gl_ctx_init(ctx, gl, params))
|
|
goto uninit;
|
|
|
|
p->vsync_duration = -1;
|
|
p->last_skipped_vsyncs = -1;
|
|
p->last_queue_display_time = -1;
|
|
|
|
return true;
|
|
|
|
uninit:
|
|
glx_uninit(ctx);
|
|
return false;
|
|
}
|
|
|
|
static bool glx_init_probe(struct ra_ctx *ctx)
|
|
{
|
|
if (!glx_init(ctx))
|
|
return false;
|
|
|
|
struct priv *p = ctx->priv;
|
|
if (!(p->gl.mpgl_caps & MPGL_CAP_VDPAU)) {
|
|
MP_VERBOSE(ctx, "No vdpau support found - probing more things.\n");
|
|
glx_uninit(ctx);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void resize(struct ra_ctx *ctx)
|
|
{
|
|
ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
|
|
}
|
|
|
|
static bool glx_reconfig(struct ra_ctx *ctx)
|
|
{
|
|
vo_x11_config_vo_window(ctx->vo);
|
|
resize(ctx);
|
|
return true;
|
|
}
|
|
|
|
static int glx_control(struct ra_ctx *ctx, int *events, int request, void *arg)
|
|
{
|
|
int ret = vo_x11_control(ctx->vo, events, request, arg);
|
|
if (*events & VO_EVENT_RESIZE)
|
|
resize(ctx);
|
|
return ret;
|
|
}
|
|
|
|
static void glx_wakeup(struct ra_ctx *ctx)
|
|
{
|
|
vo_x11_wakeup(ctx->vo);
|
|
}
|
|
|
|
static void glx_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
|
|
{
|
|
vo_x11_wait_events(ctx->vo, until_time_us);
|
|
}
|
|
|
|
const struct ra_ctx_fns ra_ctx_glx = {
|
|
.type = "opengl",
|
|
.name = "x11",
|
|
.reconfig = glx_reconfig,
|
|
.control = glx_control,
|
|
.wakeup = glx_wakeup,
|
|
.wait_events = glx_wait_events,
|
|
.init = glx_init,
|
|
.uninit = glx_uninit,
|
|
};
|
|
|
|
const struct ra_ctx_fns ra_ctx_glx_probe = {
|
|
.type = "opengl",
|
|
.name = "x11probe",
|
|
.reconfig = glx_reconfig,
|
|
.control = glx_control,
|
|
.wakeup = glx_wakeup,
|
|
.wait_events = glx_wait_events,
|
|
.init = glx_init_probe,
|
|
.uninit = glx_uninit,
|
|
};
|