summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefano Pigozzi <stefano.pigozzi@gmail.com>2014-11-23 20:06:05 +0100
committerStefano Pigozzi <stefano.pigozzi@gmail.com>2015-01-23 09:14:41 +0100
commitc29ab5a46b2676d013e4294ee719d83f6bc469b6 (patch)
treea20afc45d995e23e8a4c82575ba93994aade311a
parent86f4fcf1e2b7dc6a4aba343465a5bf153d37c7f9 (diff)
downloadmpv-c29ab5a46b2676d013e4294ee719d83f6bc469b6.tar.bz2
mpv-c29ab5a46b2676d013e4294ee719d83f6bc469b6.tar.xz
vo_opengl: add smoothmotion frame blending
SmoothMotion is a way to time and blend frames made popular by MadVR. It's intended behaviour is to remove stuttering caused by mismatches between the display refresh rate and the video fps, while preserving the video's original artistic qualities (no soap opera effect). It's supposed to make 24fps video playback on 60hz monitors as close as possible to a 24hz monitor. Instead of drawing a frame once once it's pts has passed the vsync time, we redraw at the display refresh rate, and if we detect the vsync is between two frames we interpolated them (depending on their position relative to the vsync). We actually interpolate as few frames as possible to avoid a blur effect as much as possible. For example, if we were to play back a 1fps video on a 60hz monitor, we would blend at most on 1 vsync for each frame (while the other 59 vsyncs would be rendered as is). Frame interpolation is always done before scaling and in linear light when possible (an ICC profile is used, or :srgb is used).
-rw-r--r--DOCS/man/vo.rst9
-rw-r--r--video/out/gl_video.c139
-rw-r--r--video/out/gl_video.h5
-rw-r--r--video/out/gl_video_shaders.glsl14
-rw-r--r--video/out/vo.c52
-rw-r--r--video/out/vo.h13
-rw-r--r--video/out/vo_opengl.c25
-rw-r--r--video/out/vo_opengl_cb.c2
8 files changed, 239 insertions, 20 deletions
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 6190526bd7..e0845d0e54 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -572,6 +572,15 @@ Available video output drivers are:
Color used to draw parts of the mpv window not covered by video.
See ``--osd-color`` option how colors are defined.
+ ``smoothmotion``
+ Use frame interpolation to reduce stuttering caused by mismatches in
+ video fps and display refresh rate (similar to MadVR's smoothmotion,
+ thus the naming).
+
+ ``smoothmotion-threshold=<threshold>``
+ Mix threshold at which interpolation is skipped (default: 0.0 – never
+ skip).
+
``opengl-hq``
Same as ``opengl``, but with default settings for high quality rendering.
diff --git a/video/out/gl_video.c b/video/out/gl_video.c
index 7a40338ac1..2eeb86f353 100644
--- a/video/out/gl_video.c
+++ b/video/out/gl_video.c
@@ -122,6 +122,13 @@ struct fbotex {
int vp_x, vp_y, vp_w, vp_h; // viewport of fbo / used part of the texture
};
+struct fbosurface {
+ struct fbotex fbotex;
+ int64_t pts;
+};
+
+#define FBOSURFACES_MAX 2
+
struct gl_video {
GL *gl;
@@ -139,7 +146,7 @@ struct gl_video {
GLuint vao;
GLuint osd_programs[SUBBITMAP_COUNT];
- GLuint indirect_program, scale_sep_program, final_program;
+ GLuint indirect_program, scale_sep_program, final_program, inter_program;
struct osd_state *osd_state;
struct mpgl_osd *osd;
@@ -176,6 +183,9 @@ struct gl_video {
struct fbotex indirect_fbo; // RGB target
struct fbotex scale_sep_fbo; // first pass when doing 2 pass scaling
+ struct fbotex inter_fbo; // interpolation target
+ struct fbosurface surfaces[FBOSURFACES_MAX];
+ size_t surface_num;
// state for luma (0) and chroma (1) scalers
struct scaler scalers[2];
@@ -402,7 +412,9 @@ const struct m_sub_options gl_video_conf = {
{"blend", 2})),
OPT_FLAG("rectangle-textures", use_rectangle, 0),
OPT_COLOR("background", background, 0),
-
+ OPT_FLAG("smoothmotion", smoothmotion, 0),
+ OPT_FLOAT("smoothmotion-threshold", smoothmotion_threshold,
+ CONF_RANGE, .min = 0, .max = 0.5),
OPT_REMOVED("approx-gamma", "this is always enabled now"),
OPT_REMOVED("cscale-down", "chroma is never downscaled"),
OPT_REMOVED("scale-sep", "this is set automatically whenever sane"),
@@ -649,6 +661,27 @@ static void fbotex_uninit(struct gl_video *p, struct fbotex *fbo)
}
}
+static void fbosurfaces_uninit(struct gl_video *p, struct fbosurface *surfaces)
+{
+
+ for (int i = 0; i < FBOSURFACES_MAX; i++)
+ if (surfaces[i].fbotex.fbo)
+ fbotex_uninit(p, &surfaces[i].fbotex);
+}
+
+static void fbosurfaces_init(struct gl_video *p, struct fbosurface *surfaces,
+ int w, int h, GLenum iformat)
+{
+ for (int i = 0; i < FBOSURFACES_MAX; i++)
+ if (!surfaces[i].fbotex.fbo)
+ fbotex_init(p, &surfaces[i].fbotex, w, h, iformat);
+}
+
+static size_t fbosurface_next(struct gl_video *p)
+{
+ return (p->surface_num + 1) % FBOSURFACES_MAX;
+}
+
static void matrix_ortho2d(float m[3][3], float x0, float x1,
float y0, float y1)
{
@@ -831,6 +864,7 @@ static void update_all_uniforms(struct gl_video *p)
update_uniforms(p, p->indirect_program);
update_uniforms(p, p->scale_sep_program);
update_uniforms(p, p->final_program);
+ update_uniforms(p, p->inter_program);
}
#define SECTION_HEADER "#!section "
@@ -1169,6 +1203,7 @@ static void compile_shaders(struct gl_video *p)
char *header_conv = talloc_strdup(tmp, "");
char *header_final = talloc_strdup(tmp, "");
+ char *header_inter = talloc_strdup(tmp, "");
char *header_sep = NULL;
if (p->image_desc.id == IMGFMT_NV12 || p->image_desc.id == IMGFMT_NV21) {
@@ -1211,12 +1246,20 @@ static void compile_shaders(struct gl_video *p)
shader_setup_scaler(&header_final, &p->scalers[0], -1);
}
+ bool use_interpolation = p->opts.smoothmotion;
+
+ if (use_interpolation) {
+ shader_def_opt(&header_inter, "FIXED_SCALE", true);
+ shader_def_opt(&header_inter, "USE_LINEAR_INTERPOLATION", 1);
+ }
+
// The indirect pass is used to preprocess the image before scaling.
bool use_indirect = false;
// Don't sample from input video textures before converting the input to
// its proper gamma.
- if (use_input_gamma || use_conv_gamma || use_linear_light || use_const_luma)
+ if (use_input_gamma || use_conv_gamma || use_linear_light ||
+ use_const_luma || use_interpolation)
use_indirect = true;
// Trivial scalers are implemented directly and efficiently by the GPU.
@@ -1261,6 +1304,12 @@ static void compile_shaders(struct gl_video *p)
create_program(p, "scale_sep", header_sep, vertex_shader, s_video);
}
+ if (use_interpolation) {
+ header_inter = t_concat(tmp, header, header_inter);
+ p->inter_program =
+ create_program(p, "inter", header_inter, vertex_shader, s_video);
+ }
+
header_final = t_concat(tmp, header, header_final);
p->final_program =
create_program(p, "final", header_final, vertex_shader, s_video);
@@ -1285,6 +1334,7 @@ static void delete_shaders(struct gl_video *p)
delete_program(gl, &p->indirect_program);
delete_program(gl, &p->scale_sep_program);
delete_program(gl, &p->final_program);
+ delete_program(gl, &p->inter_program);
}
static void get_scale_factors(struct gl_video *p, double xy[2])
@@ -1528,6 +1578,14 @@ static void reinit_rendering(struct gl_video *p)
if (p->indirect_program && !p->indirect_fbo.fbo)
fbotex_init(p, &p->indirect_fbo, w, h, p->opts.fbo_format);
+ if (p->inter_program && !p->inter_fbo.fbo) {
+ fbotex_init(p, &p->inter_fbo, w, h, p->opts.fbo_format);
+ }
+
+ if (p->inter_program) {
+ fbosurfaces_init(p, p->surfaces, w, h, p->opts.fbo_format);
+ }
+
recreate_osd(p);
}
@@ -1733,8 +1791,10 @@ static void uninit_video(struct gl_video *p)
}
mp_image_unrefp(&vimg->mpi);
+ fbotex_uninit(p, &p->inter_fbo);
fbotex_uninit(p, &p->indirect_fbo);
fbotex_uninit(p, &p->scale_sep_fbo);
+ fbosurfaces_uninit(p, p->surfaces);
// Invalidate image_params to ensure that gl_video_config() will call
// init_video() on uninitialized gl_video.
@@ -1819,8 +1879,54 @@ static void handle_pass(struct gl_video *p, struct pass *chain,
};
}
+static void gl_video_interpolate_frame(struct gl_video *p,
+ struct pass *chain,
+ struct frame_timing *t)
+{
+ GL *gl = p->gl;
+ double inter_coeff = 0.0;
+ int64_t prev_pts = p->surfaces[fbosurface_next(p)].pts;
+
+ if (prev_pts < t->pts) {
+ MP_STATS(p, "new-pts");
+ // fbosurface 0 is already bound from the caller
+ p->surfaces[p->surface_num].pts = t->pts;
+ p->surface_num = fbosurface_next(p);
+ gl->ActiveTexture(GL_TEXTURE0 + 1);
+ gl->BindTexture(p->gl_target, p->surfaces[p->surface_num].fbotex.texture);
+ gl->ActiveTexture(GL_TEXTURE0);
+ MP_DBG(p, "frame ppts: %lld, pts: %lld, vsync: %lld, DIFF: %lld\n",
+ (long long)prev_pts, (long long)t->pts,
+ (long long)t->next_vsync, (long long)t->next_vsync - t->pts);
+ if (prev_pts < t->next_vsync && t->pts > t->next_vsync) {
+ double N = t->next_vsync - prev_pts;
+ double P = t->pts - prev_pts;
+ double prev_pts_component = N / P;
+ float ts = p->opts.smoothmotion_threshold;
+ inter_coeff = 1 - prev_pts_component;
+ inter_coeff = inter_coeff < 0.0 + ts ? 0.0 : inter_coeff;
+ inter_coeff = inter_coeff > 1.0 - ts ? 1.0 : inter_coeff;
+ MP_DBG(p, "inter frame ppts: %lld, pts: %lld, "
+ "vsync: %lld, mix: %f\n",
+ (long long)prev_pts, (long long)t->pts,
+ (long long)t->next_vsync, inter_coeff);
+ MP_STATS(p, "frame-mix");
+
+ // the value is scaled to fit in the graph with the completely
+ // unrelated "phase" value (which is stupid)
+ MP_STATS(p, "value-timed %lld %f mix-value",
+ (long long)t->pts, inter_coeff * 10000);
+ }
+ }
+
+ gl->UseProgram(p->inter_program);
+ GLint loc = gl->GetUniformLocation(p->inter_program, "inter_coeff");
+ gl->Uniform1f(loc, inter_coeff);
+ handle_pass(p, chain, &p->inter_fbo, p->inter_program);
+}
+
// (fbo==0 makes BindFramebuffer select the screen backbuffer)
-void gl_video_render_frame(struct gl_video *p, int fbo)
+void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
{
GL *gl = p->gl;
struct video_image *vimg = &p->image;
@@ -1844,7 +1950,7 @@ void gl_video_render_frame(struct gl_video *p, int fbo)
}
// Order of processing:
- // [indirect -> [scale_sep ->]] final
+ // [indirect -> [interpolate -> [scale_sep ->]]] final
GLuint imgtex[4] = {0};
set_image_textures(p, vimg, imgtex);
@@ -1859,7 +1965,18 @@ void gl_video_render_frame(struct gl_video *p, int fbo)
},
};
- handle_pass(p, &chain, &p->indirect_fbo, p->indirect_program);
+ int64_t prev_pts = p->surfaces[fbosurface_next(p)].pts;
+ struct fbotex *indirect_target;
+ if (p->inter_program && t && prev_pts < t->pts) {
+ indirect_target = &p->surfaces[p->surface_num].fbotex;
+ } else {
+ indirect_target = &p->indirect_fbo;
+ }
+
+ handle_pass(p, &chain, indirect_target, p->indirect_program);
+
+ if (t && p->inter_program)
+ gl_video_interpolate_frame(p, &chain, t);
// Clip to visible height so that separate scaling scales the visible part
// only (and the target FBO texture can have a bounded size).
@@ -2403,6 +2520,14 @@ void gl_video_unset_gl_state(struct gl_video *p)
}
}
+void gl_video_reset(struct gl_video *p)
+{
+ for (int i = 0; i < FBOSURFACES_MAX; i++) {
+ p->surfaces[i].pts = 0;
+ }
+ p->surface_num = 0;
+}
+
// dest = src.<w> (always using 4 components)
static void packed_fmt_swizzle(char w[5], const struct fmt_entry *texfmt,
const struct packed_fmt_entry *fmt)
@@ -2697,7 +2822,7 @@ void gl_video_resize_redraw(struct gl_video *p, int w, int h)
{
p->vp_w = w;
p->vp_h = h;
- gl_video_render_frame(p, 0);
+ gl_video_render_frame(p, 0, NULL);
}
void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec)
diff --git a/video/out/gl_video.h b/video/out/gl_video.h
index b287658bef..f62a292785 100644
--- a/video/out/gl_video.h
+++ b/video/out/gl_video.h
@@ -53,6 +53,8 @@ struct gl_video_opts {
int chroma_location;
int use_rectangle;
struct m_color background;
+ int smoothmotion;
+ float smoothmotion_threshold;
};
extern const struct m_sub_options gl_video_conf;
@@ -69,7 +71,7 @@ void gl_video_config(struct gl_video *p, struct mp_image_params *params);
void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b);
void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d);
void gl_video_upload_image(struct gl_video *p, struct mp_image *img);
-void gl_video_render_frame(struct gl_video *p, int fbo);
+void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t);
struct mp_image *gl_video_download_image(struct gl_video *p);
void gl_video_resize(struct gl_video *p, struct mp_rect *window,
struct mp_rect *src, struct mp_rect *dst,
@@ -84,6 +86,7 @@ void gl_video_resize_redraw(struct gl_video *p, int w, int h);
void gl_video_set_gl_state(struct gl_video *p);
void gl_video_unset_gl_state(struct gl_video *p);
+void gl_video_reset(struct gl_video *p);
struct gl_hwdec;
void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec);
diff --git a/video/out/gl_video_shaders.glsl b/video/out/gl_video_shaders.glsl
index e40a94e185..c9398c8cd5 100644
--- a/video/out/gl_video_shaders.glsl
+++ b/video/out/gl_video_shaders.glsl
@@ -190,6 +190,7 @@ uniform float filter_param1_l;
uniform float filter_param1_c;
uniform float antiring_factor;
uniform vec2 dither_size;
+uniform float inter_coeff;
in vec2 texcoord;
DECLARE_FRAGPARMS
@@ -374,7 +375,18 @@ void main() {
#ifndef USE_CONV
#define USE_CONV 0
#endif
-#if USE_CONV == CONV_PLANAR
+#ifndef USE_LINEAR_INTERPOLATION
+#define USE_LINEAR_INTERPOLATION 0
+#endif
+#if USE_LINEAR_INTERPOLATION == 1
+ vec4 acolor = mix(
+ texture(texture0, texcoord),
+ texture(texture1, texcoord),
+ inter_coeff);
+ // debug code to visually check the interpolation amount
+ // vec4 acolor = texture(texture0, texcoord) -
+ // inter_coeff * texture(texture1, texcoord);
+#elif USE_CONV == CONV_PLANAR
vec4 acolor = vec4(SAMPLE(texture0, textures_size[0], texcoord).r,
SAMPLE_C(texture1, textures_size[1], chr_texcoord).r,
SAMPLE_C(texture2, textures_size[2], chr_texcoord).r,
diff --git a/video/out/vo.c b/video/out/vo.c
index 0d135c2842..943e99d246 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -127,6 +127,8 @@ struct vo_internal {
bool want_redraw; // redraw request from VO to player
bool send_reset; // send VOCTRL_RESET
bool paused;
+ bool vsync_timed; // the VO redraws itself as fast as possible
+ // at every vsync
int queued_events;
int64_t flip_queue_offset; // queue flip events at most this much in advance
@@ -144,6 +146,7 @@ struct vo_internal {
// --- The following fields can be accessed from the VO thread only
int64_t vsync_interval;
+ int64_t vsync_interval_approx;
int64_t last_flip;
char *window_title;
};
@@ -486,7 +489,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts)
if (next_pts > now)
r = false;
if (!in->wakeup_pts || next_pts < in->wakeup_pts) {
- in->wakeup_pts = next_pts;
+ in->wakeup_pts = in->vsync_timed ? 0 : next_pts;
wakeup_locked(vo);
}
}
@@ -507,7 +510,7 @@ void vo_queue_frame(struct vo *vo, struct mp_image *image,
in->frame_queued = image;
in->frame_pts = pts_us;
in->frame_duration = duration;
- in->wakeup_pts = in->frame_pts + MPMAX(duration, 0);
+ in->wakeup_pts = in->vsync_timed ? 0 : in->frame_pts + MPMAX(duration, 0);
wakeup_locked(vo);
pthread_mutex_unlock(&in->lock);
}
@@ -545,7 +548,13 @@ static bool render_frame(struct vo *vo)
int64_t pts = in->frame_pts;
int64_t duration = in->frame_duration;
struct mp_image *img = in->frame_queued;
- if (!img) {
+
+ if (!img && (!in->vsync_timed || in->paused || pts <= 0)) {
+ pthread_mutex_unlock(&in->lock);
+ return false;
+ }
+
+ if (in->vsync_timed && !in->hasframe) {
pthread_mutex_unlock(&in->lock);
return false;
}
@@ -556,7 +565,8 @@ static bool render_frame(struct vo *vo)
in->frame_queued = NULL;
// The next time a flip (probably) happens.
- int64_t next_vsync = prev_sync(vo, mp_time_us()) + in->vsync_interval;
+ int64_t prev_vsync = prev_sync(vo, mp_time_us());
+ int64_t next_vsync = prev_vsync + in->vsync_interval;
int64_t end_time = pts + duration;
if (!in->hasframe_rendered)
@@ -568,6 +578,18 @@ static bool render_frame(struct vo *vo)
// Even if we're hopelessly behind, rather degrade to 10 FPS playback,
// instead of just freezing the display forever.
in->dropped_frame &= mp_time_us() - in->last_flip < 100 * 1000;
+ in->dropped_frame &= in->vsync_timed && !!img;
+
+ if (in->vsync_timed && !img && in->hasframe_rendered &&
+ prev_vsync > pts + in->vsync_interval_approx) {
+ // we are very late with the frame and using vsync timing: probably
+ // no new frames are coming in. This must be done whether or not
+ // framedrop is enabled.
+ in->dropped_frame = false;
+ in->rendering = false;
+ pthread_mutex_unlock(&in->lock);
+ return false;
+ }
if (in->dropped_frame) {
in->dropped_image = img;
@@ -578,9 +600,21 @@ static bool render_frame(struct vo *vo)
MP_STATS(vo, "start video");
- vo->driver->draw_image(vo, img);
+ if (in->vsync_timed) {
+ struct frame_timing t = (struct frame_timing) {
+ .pts = pts,
+ .next_vsync = next_vsync,
+ };
+ vo->driver->draw_image_timed(vo, img, &t);
+ } else {
+ vo->driver->draw_image(vo, img);
+ }
- int64_t target = pts - in->flip_queue_offset;
+ int64_t target = !in->vsync_timed ?
+ pts - in->flip_queue_offset :
+ // this is a heuristic that wakes the thread up some
+ // time before the next vsync
+ next_vsync - MPMIN(in->vsync_interval / 3, 4e3);
while (1) {
int64_t now = mp_time_us();
if (target <= now)
@@ -594,6 +628,8 @@ static bool render_frame(struct vo *vo)
else
vo->driver->flip_page(vo);
+ int64_t prev_flip = in->last_flip;
+
in->last_flip = -1;
vo->driver->control(vo, VOCTRL_GET_RECENT_FLIP_TIME, &in->last_flip);
@@ -601,10 +637,13 @@ static bool render_frame(struct vo *vo)
if (in->last_flip < 0)
in->last_flip = mp_time_us();
+ in->vsync_interval_approx = in->last_flip - prev_flip;
+
long phase = in->last_flip % in->vsync_interval;
MP_DBG(vo, "phase: %ld\n", phase);
MP_STATS(vo, "value %ld phase", phase);
+ MP_STATS(vo, "display");
MP_STATS(vo, "end video");
pthread_mutex_lock(&in->lock);
@@ -669,6 +708,7 @@ static void *vo_thread(void *ptr)
mpthread_set_name("vo");
int r = vo->driver->preinit(vo) ? -1 : 0;
+ vo->driver->control(vo, VOCTRL_GET_VSYNC_TIMED, &in->vsync_timed);
mp_rendezvous(vo, r); // init barrier
if (r < 0)
return NULL;
diff --git a/video/out/vo.h b/video/out/vo.h
index d943d4ad78..33a2513c1f 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -106,6 +106,8 @@ enum mp_voctrl {
VOCTRL_GET_RECENT_FLIP_TIME, // int64_t* (using mp_time_us())
VOCTRL_GET_PREF_DEINT, // int*
+
+ VOCTRL_GET_VSYNC_TIMED, // bool*
};
// VOCTRL_SET_EQUALIZER
@@ -171,6 +173,11 @@ struct vo_extra {
struct mpv_opengl_cb_context *opengl_cb_context;
};
+struct frame_timing {
+ int64_t pts;
+ int64_t next_vsync;
+};
+
struct vo_driver {
// Encoding functionality, which can be invoked via --o only.
bool encode;
@@ -218,6 +225,12 @@ struct vo_driver {
*/
void (*draw_image)(struct vo *vo, struct mp_image *mpi);
+ /* Like draw image, but is called before every vsync with timing
+ * information
+ */
+ void (*draw_image_timed)(struct vo *vo, struct mp_image *mpi,
+ struct frame_timing *t);
+
/*
* Blit/Flip buffer to the screen. Must be called after each frame!
*/
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
index 058b305c2f..d3f1e7da7d 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -154,7 +154,8 @@ static void flip_page(struct vo *vo)
mpgl_unlock(p->glctx);
}
-static void draw_image(struct vo *vo, mp_image_t *mpi)
+static void draw_image_timed(struct vo *vo, mp_image_t *mpi,
+ struct frame_timing *t)
{
struct gl_priv *p = vo->priv;
GL *gl = p->gl;
@@ -164,8 +165,9 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
mpgl_lock(p->glctx);
- gl_video_upload_image(p->renderer, mpi);
- gl_video_render_frame(p->renderer, 0);
+ if (mpi)
+ gl_video_upload_image(p->renderer, mpi);
+ gl_video_render_frame(p->renderer, 0, t);
// The playloop calls this last before waiting some time until it decides
// to call flip_page(). Tell OpenGL to start execution of the GPU commands
@@ -178,6 +180,11 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
mpgl_unlock(p->glctx);
}
+static void draw_image(struct vo *vo, mp_image_t *mpi)
+{
+ draw_image_timed(vo, mpi, NULL);
+}
+
static int query_format(struct vo *vo, int format)
{
struct gl_priv *p = vo->priv;
@@ -361,13 +368,21 @@ static int control(struct vo *vo, uint32_t request, void *data)
return true;
case VOCTRL_REDRAW_FRAME:
mpgl_lock(p->glctx);
- gl_video_render_frame(p->renderer, 0);
+ gl_video_render_frame(p->renderer, 0, NULL);
mpgl_unlock(p->glctx);
return true;
case VOCTRL_SET_COMMAND_LINE: {
char *arg = data;
return reparse_cmdline(p, arg);
}
+ case VOCTRL_GET_VSYNC_TIMED:
+ *(bool *)data = p->renderer_opts->smoothmotion;
+ return VO_TRUE;
+ case VOCTRL_RESET:
+ mpgl_lock(p->glctx);
+ gl_video_reset(p->renderer);
+ mpgl_unlock(p->glctx);
+ return true;
}
mpgl_lock(p->glctx);
@@ -474,6 +489,7 @@ const struct vo_driver video_out_opengl = {
.reconfig = reconfig,
.control = control,
.draw_image = draw_image,
+ .draw_image_timed = draw_image_timed,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct gl_priv),
@@ -489,6 +505,7 @@ const struct vo_driver video_out_opengl_hq = {
.reconfig = reconfig,
.control = control,
.draw_image = draw_image,
+ .draw_image_timed = draw_image_timed,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct gl_priv),
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index 1a2e44f718..091a376dfa 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -339,7 +339,7 @@ int mpv_opengl_cb_render(struct mpv_opengl_cb_context *ctx, int fbo, int vp[4])
if (mpi)
gl_video_upload_image(ctx->renderer, mpi);
- gl_video_render_frame(ctx->renderer, fbo);
+ gl_video_render_frame(ctx->renderer, fbo, NULL);
gl_video_unset_gl_state(ctx->renderer);