vo_opengl: refactor smoothmotion -> interpolation

This replaces the old smoothmotion code by a more flexible tscale
option, which essentially allows any scaler to be used for interpolating
frames. (The actual "smoothmotion" scaler which behaves identical to the
old code does not currently exist, but it will be re-added in a later commit)

The only odd thing is that larger filters require a larger queue size
offset, which is currently set dynamically as it introduces some issues
when pausing or framestepping. Filters with a lower radius are not
affected as much, so this is identical to the old smoothmotion if the
smoothmotion interpolator is used.
This commit is contained in:
Niklas Haas 2015-03-13 19:30:31 +01:00
parent 6f3292813f
commit 44a78a2be2
5 changed files with 188 additions and 129 deletions

View File

@ -426,11 +426,29 @@ Available video output drivers are:
debug OpenGL context (which does nothing with current graphics drivers debug OpenGL context (which does nothing with current graphics drivers
as of this writing). as of this writing).
``interpolation``
Reduce stuttering caused by mismatches in the video fps and display
refresh rate (also known as judder).
This essentially attempts to interpolate the missing frames by
convoluting the video along the temporal axis. The filter used can be
controlled using the ``tscale`` setting.
Note that this relies on vsync to work, see ``swapinterval`` for more
information.
``swapinterval=<n>`` ``swapinterval=<n>``
Interval in displayed frames between two buffer swaps. Interval in displayed frames between two buffer swaps.
1 is equivalent to enable VSYNC, 0 to disable VSYNC. Defaults to 1 if 1 is equivalent to enable VSYNC, 0 to disable VSYNC. Defaults to 1 if
not specified. not specified.
Note that this depends on proper OpenGL vsync support. On some platforms
and drivers, this only works reliably when in fullscreen mode. It may
also require driver-specific hacks if using multiple monitors, to
ensure mpv syncs to the right one. Compositing window managers can
also lead to bad results, as can missing or incorrect display FPS
information (see ``--display-fps``).
``cscale=<filter>`` ``cscale=<filter>``
As ``scale``, but for interpolating chroma information. If the image As ``scale``, but for interpolating chroma information. If the image
is not subsampled, this option is ignored entirely. is not subsampled, this option is ignored entirely.
@ -445,6 +463,18 @@ Available video output drivers are:
See ``scale-param1``, ``scale-param2``, ``scale-radius`` and See ``scale-param1``, ``scale-param2``, ``scale-radius`` and
``scale-antiring``. ``scale-antiring``.
``tscale=<filter>``, ``tscale-param1``, ``tscale-param2``, ``tscale-antiring``
The filter used for interpolating the temporal axis (frames). This is
only used if ``interpolation`` is enabled. The only valid choices
for ``tscale`` are separable convolution filters (use ``tscale=help``
to get a list). The other options (``tscale-param1`` etc.) are
analogous to their ``scale`` counterparts. The default is ``mitchell``.
Note that the maximum supported filter radius is currently 3, and that
using filters with larger radius may introduce isues when pausing or
framestepping, proportional to the radius used. It is recommended to
stick to a radius of 1 or 2.
``linear-scaling`` ``linear-scaling``
Scale in linear light. This is automatically enabled if Scale in linear light. This is automatically enabled if
``target-prim``, ``target-trc``, ``icc-profile`` or ``target-prim``, ``target-trc``, ``icc-profile`` or
@ -636,48 +666,6 @@ Available video output drivers are:
Color used to draw parts of the mpv window not covered by video. Color used to draw parts of the mpv window not covered by video.
See ``--osd-color`` option how colors are defined. See ``--osd-color`` option how colors are defined.
``smoothmotion``
Reduce stuttering caused by mismatches in video fps and display
refresh rate (also known as judder).
Instead of drawing source frames for variable durations, smoothmotion
will blend frames that overlap the transition between two frames in
the source material.
For example, a 24 Hz clip played back on a 60 Hz display would normally
result in a pattern like this::
A A A B B C C C D D E E E F F
which has different lengths, alternating between 3 and 2. This
difference in frame duration is what causes judder.
With smoothmotion enabled, the pattern changes to::
A A A+B B B C C C+D D D E E E+F F F
where A+B is a blend of A and B. In this pattern, each frame gets a
(consistent) duration of roughly 2.5 - resulting in smooth motion.
GPU drivers or compositing window managers overriding vsync behavior
can lead to bad results. If the framerate is close to or over the
display refresh rate, results can be bad as well.
.. note:: On systems other than Linux or OS X, you currently must set
the ``--display-fps`` option, or the results will be bad.
``smoothmotion-threshold=<0.0-0.5>``
Mix threshold at which interpolation is skipped (default: 0.0 never
skip).
For example, with a ``smoothmotion-threshold`` of 0.1, if the
smoothmotion algorithm would try to blend two frames by a ratio of
95% A + 5% B, it would simply display A instead. (Since the
distance, 0.05, is lower than the threshold)
Setting this to 0.5 would be similar to disabling smoothmotion
completely, since it would always just display the nearest frame.
``opengl-hq`` ``opengl-hq``
Same as ``opengl``, but with default settings for high quality rendering. Same as ``opengl``, but with default settings for high quality rendering.

View File

@ -49,7 +49,7 @@
// Other texture units are reserved for specific purposes // Other texture units are reserved for specific purposes
#define TEXUNIT_SCALERS TEXUNIT_VIDEO_NUM #define TEXUNIT_SCALERS TEXUNIT_VIDEO_NUM
#define TEXUNIT_3DLUT (TEXUNIT_SCALERS+2) #define TEXUNIT_3DLUT (TEXUNIT_SCALERS+3)
#define TEXUNIT_DITHER (TEXUNIT_3DLUT+1) #define TEXUNIT_DITHER (TEXUNIT_3DLUT+1)
// scale/cscale arguments that map directly to shader filter routines. // scale/cscale arguments that map directly to shader filter routines.
@ -63,9 +63,9 @@ static const char *const fixed_scale_filters[] = {
}; };
// must be sorted, and terminated with 0 // must be sorted, and terminated with 0
// 2 & 6 are special-cased, the rest can be generated with WEIGHTS_N().
int filter_sizes[] = int filter_sizes[] =
{2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 0}; {2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 0};
int tscale_sizes[] = {2, 4, 6, 0}; // limited by TEXUNIT_VIDEO_NUM
struct vertex_pt { struct vertex_pt {
float x, y; float x, y;
@ -129,7 +129,7 @@ struct fbosurface {
int64_t pts; int64_t pts;
}; };
#define FBOSURFACES_MAX 6 #define FBOSURFACES_MAX 10
struct src_tex { struct src_tex {
GLuint gl_tex; GLuint gl_tex;
@ -182,12 +182,12 @@ struct gl_video {
struct fbotex chroma_merge_fbo; struct fbotex chroma_merge_fbo;
struct fbosurface surfaces[FBOSURFACES_MAX]; struct fbosurface surfaces[FBOSURFACES_MAX];
size_t surface_idx; int surface_idx;
size_t surface_now; int surface_now;
bool is_interpolated; bool is_interpolated;
// state for luma (0) and chroma (1) scalers // state for luma (0), chroma (1) and temporal (2) scalers
struct scaler scalers[2]; struct scaler scalers[3];
struct mp_csp_equalizer video_eq; struct mp_csp_equalizer video_eq;
@ -320,10 +320,10 @@ const struct gl_video_opts gl_video_opts_def = {
.fbo_format = GL_RGBA, .fbo_format = GL_RGBA,
.sigmoid_center = 0.75, .sigmoid_center = 0.75,
.sigmoid_slope = 6.5, .sigmoid_slope = 6.5,
.scalers = { "bilinear", "bilinear" }, .scalers = { "bilinear", "bilinear", "mitchell" },
.dscaler = "bilinear", .dscaler = "bilinear",
.scaler_params = {{NAN, NAN}, {NAN, NAN}}, .scaler_params = {{NAN, NAN}, {NAN, NAN}, {NAN, NAN}},
.scaler_radius = {3, 3}, .scaler_radius = {3, 3, 3},
.alpha_mode = 2, .alpha_mode = 2,
.background = {0, 0, 0, 255}, .background = {0, 0, 0, 255},
.gamma = 1.0f, .gamma = 1.0f,
@ -338,10 +338,10 @@ const struct gl_video_opts gl_video_opts_hq_def = {
.sigmoid_center = 0.75, .sigmoid_center = 0.75,
.sigmoid_slope = 6.5, .sigmoid_slope = 6.5,
.sigmoid_upscaling = 1, .sigmoid_upscaling = 1,
.scalers = { "spline36", "bilinear" }, .scalers = { "spline36", "bilinear", "mitchell" },
.dscaler = "mitchell", .dscaler = "mitchell",
.scaler_params = {{NAN, NAN}, {NAN, NAN}}, .scaler_params = {{NAN, NAN}, {NAN, NAN}, {NAN, NAN}},
.scaler_radius = {3, 3}, .scaler_radius = {3, 3, 3},
.alpha_mode = 2, .alpha_mode = 2,
.background = {0, 0, 0, 255}, .background = {0, 0, 0, 255},
.gamma = 1.0f, .gamma = 1.0f,
@ -372,15 +372,20 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("pbo", pbo, 0), OPT_FLAG("pbo", pbo, 0),
OPT_STRING_VALIDATE("scale", scalers[0], 0, validate_scaler_opt), OPT_STRING_VALIDATE("scale", scalers[0], 0, validate_scaler_opt),
OPT_STRING_VALIDATE("cscale", scalers[1], 0, validate_scaler_opt), OPT_STRING_VALIDATE("cscale", scalers[1], 0, validate_scaler_opt),
OPT_STRING_VALIDATE("tscale", scalers[2], 0, validate_scaler_opt),
OPT_STRING_VALIDATE("scale-down", dscaler, 0, validate_scaler_opt), OPT_STRING_VALIDATE("scale-down", dscaler, 0, validate_scaler_opt),
OPT_FLOAT("scale-param1", scaler_params[0][0], 0), OPT_FLOAT("scale-param1", scaler_params[0][0], 0),
OPT_FLOAT("scale-param2", scaler_params[0][1], 0), OPT_FLOAT("scale-param2", scaler_params[0][1], 0),
OPT_FLOAT("cscale-param1", scaler_params[1][0], 0), OPT_FLOAT("cscale-param1", scaler_params[1][0], 0),
OPT_FLOAT("cscale-param2", scaler_params[1][1], 0), OPT_FLOAT("cscale-param2", scaler_params[1][1], 0),
OPT_FLOAT("tscale-param1", scaler_params[2][0], 0),
OPT_FLOAT("tscale-param2", scaler_params[2][1], 0),
OPT_FLOATRANGE("scale-radius", scaler_radius[0], 0, 1.0, 16.0), OPT_FLOATRANGE("scale-radius", scaler_radius[0], 0, 1.0, 16.0),
OPT_FLOATRANGE("cscale-radius", scaler_radius[1], 0, 1.0, 16.0), OPT_FLOATRANGE("cscale-radius", scaler_radius[1], 0, 1.0, 16.0),
OPT_FLOATRANGE("tscale-radius", scaler_radius[2], 0, 1.0, 3.0),
OPT_FLOATRANGE("scale-antiring", scaler_antiring[0], 0, 0.0, 1.0), OPT_FLOATRANGE("scale-antiring", scaler_antiring[0], 0, 0.0, 1.0),
OPT_FLOATRANGE("cscale-antiring", scaler_antiring[1], 0, 0.0, 1.0), OPT_FLOATRANGE("cscale-antiring", scaler_antiring[1], 0, 0.0, 1.0),
OPT_FLOATRANGE("tscale-antiring", scaler_antiring[2], 0, 0.0, 1.0),
OPT_FLAG("scaler-resizes-only", scaler_resizes_only, 0), OPT_FLAG("scaler-resizes-only", scaler_resizes_only, 0),
OPT_FLAG("linear-scaling", linear_scaling, 0), OPT_FLAG("linear-scaling", linear_scaling, 0),
OPT_FLAG("fancy-downscaling", fancy_downscaling, 0), OPT_FLAG("fancy-downscaling", fancy_downscaling, 0),
@ -416,13 +421,12 @@ const struct m_sub_options gl_video_conf = {
{"blend", 2})), {"blend", 2})),
OPT_FLAG("rectangle-textures", use_rectangle, 0), OPT_FLAG("rectangle-textures", use_rectangle, 0),
OPT_COLOR("background", background, 0), OPT_COLOR("background", background, 0),
OPT_FLAG("smoothmotion", smoothmotion, 0), OPT_FLAG("interpolation", interpolation, 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("approx-gamma", "this is always enabled now"),
OPT_REMOVED("cscale-down", "chroma is never downscaled"), OPT_REMOVED("cscale-down", "chroma is never downscaled"),
OPT_REMOVED("scale-sep", "this is set automatically whenever sane"), OPT_REMOVED("scale-sep", "this is set automatically whenever sane"),
OPT_REMOVED("indirect", "this is set automatically whenever sane"), OPT_REMOVED("indirect", "this is set automatically whenever sane"),
OPT_REMOVED("smoothmotion-threshold", "to be readded as a proper scaler"),
OPT_REPLACED("lscale", "scale"), OPT_REPLACED("lscale", "scale"),
OPT_REPLACED("lscale-down", "scale-down"), OPT_REPLACED("lscale-down", "scale-down"),
@ -435,6 +439,7 @@ const struct m_sub_options gl_video_conf = {
OPT_REPLACED("cradius", "cscale-radius"), OPT_REPLACED("cradius", "cscale-radius"),
OPT_REPLACED("cantiring", "cscale-antiring"), OPT_REPLACED("cantiring", "cscale-antiring"),
OPT_REPLACED("srgb", "target-prim=srgb:target-trc=srgb"), OPT_REPLACED("srgb", "target-prim=srgb:target-trc=srgb"),
OPT_REPLACED("smoothmotion", "interpolation"),
{0} {0}
}, },
@ -489,9 +494,10 @@ static void gl_video_reset_surfaces(struct gl_video *p)
p->surface_now = 0; p->surface_now = 0;
} }
static size_t fbosurface_next(size_t id) static inline int fbosurface_wrap(int id)
{ {
return (id+1) % FBOSURFACES_MAX; id = id % FBOSURFACES_MAX;
return id < 0 ? id + FBOSURFACES_MAX : id;
} }
static void recreate_osd(struct gl_video *p) static void recreate_osd(struct gl_video *p)
@ -517,7 +523,7 @@ static void uninit_rendering(struct gl_video *p)
{ {
GL *gl = p->gl; GL *gl = p->gl;
for (int n = 0; n < 2; n++) for (int n = 0; n < 3; n++)
uninit_scaler(p, n); uninit_scaler(p, n);
gl->DeleteTextures(1, &p->dither_texture); gl->DeleteTextures(1, &p->dither_texture);
@ -861,7 +867,7 @@ static void uninit_scaler(struct gl_video *p, int scaler_unit)
} }
static void reinit_scaler(struct gl_video *p, int scaler_unit, const char *name, static void reinit_scaler(struct gl_video *p, int scaler_unit, const char *name,
double scale_factor) double scale_factor, int sizes[])
{ {
GL *gl = p->gl; GL *gl = p->gl;
struct scaler *scaler = &p->scalers[scaler_unit]; struct scaler *scaler = &p->scalers[scaler_unit];
@ -898,8 +904,7 @@ static void reinit_scaler(struct gl_video *p, int scaler_unit, const char *name,
if (scaler->kernel->radius < 0) if (scaler->kernel->radius < 0)
scaler->kernel->radius = p->opts.scaler_radius[scaler->index]; scaler->kernel->radius = p->opts.scaler_radius[scaler->index];
scaler->insufficient = !mp_init_filter(scaler->kernel, filter_sizes, scaler->insufficient = !mp_init_filter(scaler->kernel, sizes, scale_factor);
scale_factor);
if (scaler->kernel->polar) { if (scaler->kernel->polar) {
scaler->gl_target = GL_TEXTURE_1D; scaler->gl_target = GL_TEXTURE_1D;
@ -980,18 +985,22 @@ static void pass_sample_separated_get_weights(struct gl_video *p,
} }
// Handle a single pass (either vertical or horizontal). The direction is given // Handle a single pass (either vertical or horizontal). The direction is given
// by the vector (d_x, d_y) // by the vector (d_x, d_y). If the vector is 0, then planar interpolation is
// used instead (samples from texture0 through textureN)
static void pass_sample_separated_gen(struct gl_video *p, struct scaler *scaler, static void pass_sample_separated_gen(struct gl_video *p, struct scaler *scaler,
int d_x, int d_y) int d_x, int d_y)
{ {
int N = scaler->kernel->size; int N = scaler->kernel->size;
bool use_ar = scaler->antiring > 0; bool use_ar = scaler->antiring > 0;
bool planar = d_x == 0 && d_y == 0;
GLSL(vec4 color = vec4(0.0);) GLSL(vec4 color = vec4(0.0);)
GLSLF("{\n"); GLSLF("{\n");
GLSLF("vec2 dir = vec2(%d, %d);\n", d_x, d_y); if (!planar) {
GLSL(vec2 pt = (vec2(1.0) / sample_size) * dir;) GLSLF("vec2 dir = vec2(%d, %d);\n", d_x, d_y);
GLSL(float fcoord = dot(fract(sample_pos * sample_size - vec2(0.5)), dir);) GLSL(vec2 pt = (vec2(1.0) / sample_size) * dir;)
GLSLF("vec2 base = sample_pos - fcoord * pt - pt * vec2(%d);\n", N / 2 - 1); GLSL(float fcoord = dot(fract(sample_pos * sample_size - vec2(0.5)), dir);)
GLSLF("vec2 base = sample_pos - fcoord * pt - pt * vec2(%d);\n", N / 2 - 1);
}
GLSL(vec4 c;) GLSL(vec4 c;)
if (use_ar) { if (use_ar) {
GLSL(vec4 hi = vec4(0.0);) GLSL(vec4 hi = vec4(0.0);)
@ -1000,7 +1009,11 @@ static void pass_sample_separated_gen(struct gl_video *p, struct scaler *scaler,
pass_sample_separated_get_weights(p, scaler); pass_sample_separated_get_weights(p, scaler);
GLSLF("// scaler samples\n"); GLSLF("// scaler samples\n");
for (int n = 0; n < N; n++) { for (int n = 0; n < N; n++) {
GLSLF("c = texture(sample_tex, base + pt * vec2(%d));\n", n); if (planar) {
GLSLF("c = texture(texture%d, texcoord%d);\n", n, n);
} else {
GLSLF("c = texture(sample_tex, base + pt * vec2(%d));\n", n);
}
GLSLF("color += vec4(weights[%d]) * c;\n", n); GLSLF("color += vec4(weights[%d]) * c;\n", n);
if (use_ar && (n == N/2-1 || n == N/2)) { if (use_ar && (n == N/2-1 || n == N/2)) {
GLSL(lo = min(lo, c);) GLSL(lo = min(lo, c);)
@ -1163,7 +1176,6 @@ static void pass_sample_sharpen5(struct gl_video *p, struct scaler *scaler)
double param = isnan(scaler->params[0]) ? 0.5 : scaler->params[0]; double param = isnan(scaler->params[0]) ? 0.5 : scaler->params[0];
GLSLF("color = p + t * %f;\n", param); GLSLF("color = p + t * %f;\n", param);
GLSLF("}\n"); GLSLF("}\n");
} }
// Sample. This samples from the texture ID given by src_tex. It's hardcoded to // Sample. This samples from the texture ID given by src_tex. It's hardcoded to
@ -1180,7 +1192,7 @@ static void pass_sample(struct gl_video *p, int src_tex,
int w, int h, struct gl_transform transform) int w, int h, struct gl_transform transform)
{ {
struct scaler *scaler = &p->scalers[scaler_unit]; struct scaler *scaler = &p->scalers[scaler_unit];
reinit_scaler(p, scaler_unit, name, scale_factor); reinit_scaler(p, scaler_unit, name, scale_factor, filter_sizes);
// Set up the sample parameters appropriately // Set up the sample parameters appropriately
GLSLF("#define sample_tex texture%d\n", src_tex); GLSLF("#define sample_tex texture%d\n", src_tex);
@ -1667,7 +1679,6 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
int vp_w = p->dst_rect.x1 - p->dst_rect.x0, int vp_w = p->dst_rect.x1 - p->dst_rect.x0,
vp_h = p->dst_rect.y1 - p->dst_rect.y0, vp_h = p->dst_rect.y1 - p->dst_rect.y0,
fuzz = FBOTEX_FUZZY_W | FBOTEX_FUZZY_H; fuzz = FBOTEX_FUZZY_W | FBOTEX_FUZZY_H;
size_t surface_nxt = fbosurface_next(p->surface_now);
// First of all, figure out if we have a frame availble at all, and draw // First of all, figure out if we have a frame availble at all, and draw
// it manually + reset the queue if not // it manually + reset the queue if not
@ -1679,9 +1690,27 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
p->surface_idx = p->surface_now; p->surface_idx = p->surface_now;
} }
// Figure out the queue size. For illustration, a filter radius of 2 would
// look like this: _ A [B] C D _
// A is surface_bse, B is surface_now, C is surface_nxt and D is
// surface_end.
struct scaler *tscale = &p->scalers[2];
reinit_scaler(p, 2, p->opts.scalers[2], 1, tscale_sizes);
assert(tscale->kernel && !tscale->kernel->polar);
int size = ceil(tscale->kernel->size);
assert(size <= TEXUNIT_VIDEO_NUM);
int radius = size/2;
int surface_now = p->surface_now;
int surface_nxt = fbosurface_wrap(surface_now + 1);
int surface_bse = fbosurface_wrap(surface_now - (radius-1));
int surface_end = fbosurface_wrap(surface_now + radius);
assert(fbosurface_wrap(surface_bse + size-1) == surface_end);
// Render a new frame if it came in and there's room in the queue // Render a new frame if it came in and there's room in the queue
size_t surface_dst = fbosurface_next(p->surface_idx); int surface_dst = fbosurface_wrap(p->surface_idx+1);
if (t && surface_dst != p->surface_now && if (t && surface_dst != surface_bse &&
p->surfaces[p->surface_idx].pts < t->pts) { p->surfaces[p->surface_idx].pts < t->pts) {
MP_STATS(p, "new-pts"); MP_STATS(p, "new-pts");
pass_draw_frame(p); pass_draw_frame(p);
@ -1691,40 +1720,58 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
p->surface_idx = surface_dst; p->surface_idx = surface_dst;
} }
// Figure out whether the queue is "valid". A queue is invalid if the
// frames' PTS is not monotonically increasing. Anything else is invalid,
// so avoid blending incorrect data and just draw the latest frame as-is.
// Possible causes for failure of this condition include seeks, pausing,
// end of playback or start of playback.
bool valid = true;
for (int i = surface_bse; i != surface_end; i = fbosurface_wrap(i+1)) {
if (!p->surfaces[i].pts ||
p->surfaces[fbosurface_wrap(i+1)].pts < p->surfaces[i].pts) {
valid = false;
break;
}
}
// Finally, draw the right mix of frames to the screen. // Finally, draw the right mix of frames to the screen.
pass_load_fbotex(p, &p->surfaces[p->surface_now].fbotex, 0, vp_w, vp_h); if (!t || !valid) {
if (!t || p->surfaces[surface_nxt].pts < p->surfaces[p->surface_now].pts) { // surface_now is guaranteed to be valid, so we can safely use it.
// No next frame available (eg. start of playback, after reconfigure pass_load_fbotex(p, &p->surfaces[surface_now].fbotex, 0, vp_w, vp_h);
// or end of file, so just draw the current frame instead of blending.
// Also occurs when no timing information is available (eg. paused)
GLSL(vec4 color = texture(texture0, texcoord0);) GLSL(vec4 color = texture(texture0, texcoord0);)
p->is_interpolated = false; p->is_interpolated = false;
} else { } else {
int64_t next_pts = p->surfaces[surface_nxt].pts, int64_t pts_now = p->surfaces[surface_now].pts,
vsync_interval = t->next_vsync - t->prev_vsync; pts_nxt = p->surfaces[surface_nxt].pts;
double inter_coeff = (double)(next_pts - t->next_vsync) / vsync_interval, double fscale = pts_nxt - pts_now;
threshold = p->opts.smoothmotion_threshold; double fcoord = (t->next_vsync - pts_now) / fscale;
inter_coeff = inter_coeff <= 0.0 + threshold ? 0.0 : inter_coeff; gl_sc_uniform_f(p->sc, "fcoord", fcoord);
inter_coeff = inter_coeff >= 1.0 - threshold ? 1.0 : inter_coeff;
inter_coeff = 1.0 - inter_coeff;
gl_sc_uniform_f(p->sc, "inter_coeff", inter_coeff);
p->is_interpolated = inter_coeff > 0;
MP_STATS(p, "frame-mix"); MP_STATS(p, "frame-mix");
MP_DBG(p, "inter frame ppts: %lld, pts: %lld, vsync: %lld, mix: %f\n", MP_DBG(p, "inter frame ppts: %lld, pts: %lld, vsync: %lld, mix: %f\n",
(long long)p->surfaces[p->surface_now].pts, (long long)pts_now, (long long)pts_nxt,
(long long)p->surfaces[surface_nxt].pts, (long long)t->next_vsync, fcoord);
(long long)t->next_vsync, inter_coeff);
for (int i = 0; i < size; i++) {
pass_load_fbotex(p, &p->surfaces[fbosurface_wrap(surface_bse+i)].fbotex,
i, vp_w, vp_h);
}
pass_sample_separated_gen(p, tscale, 0, 0);
p->is_interpolated = true;
pass_load_fbotex(p, &p->surfaces[surface_nxt].fbotex, 1, vp_w, vp_h);
GLSL(vec4 color = mix(texture(texture0, texcoord0),
texture(texture1, texcoord1),
inter_coeff);)
// Dequeue the current frame if it's no longer needed
if (t->next_vsync + vsync_interval > p->surfaces[surface_nxt].pts)
p->surface_now = surface_nxt;
} }
pass_draw_to_screen(p, fbo); pass_draw_to_screen(p, fbo);
// Dequeue frames if necessary
if (t) {
int64_t vsync_interval = t->next_vsync - t->prev_vsync;
int64_t vsync_guess = t->next_vsync + vsync_interval;
if (p->surfaces[surface_nxt].pts > p->surfaces[p->surface_now].pts
&& p->surfaces[surface_nxt].pts < vsync_guess) {
p->surface_now = surface_nxt;
surface_nxt = fbosurface_wrap(p->surface_now+1);
}
}
} }
// (fbo==0 makes BindFramebuffer select the screen backbuffer) // (fbo==0 makes BindFramebuffer select the screen backbuffer)
@ -1748,9 +1795,10 @@ void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
gl_sc_set_vao(p->sc, &p->vao); gl_sc_set_vao(p->sc, &p->vao);
if (p->opts.smoothmotion) { if (p->opts.interpolation) {
gl_video_interpolate_frame(p, fbo, t); gl_video_interpolate_frame(p, fbo, t);
} else { } else {
// Skip interpolation if there's nothing to be done
pass_draw_frame(p); pass_draw_frame(p);
pass_draw_to_screen(p, fbo); pass_draw_to_screen(p, fbo);
} }
@ -1982,9 +2030,9 @@ static void check_gl_features(struct gl_video *p)
p->use_lut_3d = false; p->use_lut_3d = false;
disabled[n_disabled++] = "color management (FBO)"; disabled[n_disabled++] = "color management (FBO)";
} }
if (p->opts.smoothmotion && !test_fbo(p, &have_fbo)) { if (p->opts.interpolation && !test_fbo(p, &have_fbo)) {
p->opts.smoothmotion = false; p->opts.interpolation = false;
disabled[n_disabled++] = "smoothmotion (FBO)"; disabled[n_disabled++] = "interpolation (FBO)";
} }
if (gl->es && p->opts.pbo) { if (gl->es && p->opts.pbo) {
p->opts.pbo = 0; p->opts.pbo = 0;
@ -2282,6 +2330,7 @@ struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct osd_state *osd
.scalers = { .scalers = {
{ .index = 0, .name = "bilinear" }, { .index = 0, .name = "bilinear" },
{ .index = 1, .name = "bilinear" }, { .index = 1, .name = "bilinear" },
{ .index = 2, .name = "mitchell" },
}, },
.sc = gl_sc_create(gl, log), .sc = gl_sc_create(gl, log),
}; };
@ -2291,16 +2340,17 @@ struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct osd_state *osd
return p; return p;
} }
// Get static string for scaler shader. // Get static string for scaler shader. If "tscale" is set to true, the
static const char *handle_scaler_opt(const char *name) // scaler must be a separable convolution filter.
static const char *handle_scaler_opt(const char *name, bool tscale)
{ {
if (name && name[0]) { if (name && name[0]) {
const struct filter_kernel *kernel = mp_find_filter_kernel(name); const struct filter_kernel *kernel = mp_find_filter_kernel(name);
if (kernel) if (kernel && (!tscale || !kernel->polar))
return kernel->name; return kernel->name;
for (const char *const *filter = fixed_scale_filters; *filter; filter++) { for (const char *const *filter = fixed_scale_filters; *filter; filter++) {
if (strcmp(*filter, name) == 0) if (strcmp(*filter, name) == 0 && !tscale)
return *filter; return *filter;
} }
} }
@ -2309,12 +2359,26 @@ static const char *handle_scaler_opt(const char *name)
// Set the options, and possibly update the filter chain too. // Set the options, and possibly update the filter chain too.
// Note: assumes all options are valid and verified by the option parser. // Note: assumes all options are valid and verified by the option parser.
void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts) void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts,
int *queue_size)
{ {
p->opts = *opts; p->opts = *opts;
for (int n = 0; n < 2; n++) for (int n = 0; n < 3; n++)
p->opts.scalers[n] = (char *)handle_scaler_opt(p->opts.scalers[n]); p->opts.scalers[n] = (char *)handle_scaler_opt(p->opts.scalers[n], n==2);
p->opts.dscaler = (char *)handle_scaler_opt(p->opts.dscaler); p->opts.dscaler = (char *)handle_scaler_opt(p->opts.dscaler, false);
// Figure out an adequate size for the interpolation queue. The larger
// the radius, the earlier we need to queue frames. This rough heuristic
// seems to work for now, but ideally we want to rework the pause/unpause
// logic to make larger queue sizes the default.
if (queue_size && p->opts.interpolation && p->opts.scalers[2]) {
const struct filter_kernel *kernel = mp_find_filter_kernel(p->opts.scalers[2]);
if (kernel) {
double radius = kernel->radius;
radius = radius > 0 ? radius : p->opts.scaler_radius[2];
*queue_size = 50e3 * (ceil(radius) - 1);
}
}
check_gl_features(p); check_gl_features(p);
uninit_rendering(p); uninit_rendering(p);
@ -2340,19 +2404,24 @@ static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
{ {
char s[20] = {0}; char s[20] = {0};
int r = 1; int r = 1;
bool tscale = bstr_equals0(name, "tscale");
if (bstr_equals0(param, "help")) { if (bstr_equals0(param, "help")) {
r = M_OPT_EXIT - 1; r = M_OPT_EXIT - 1;
} else { } else {
snprintf(s, sizeof(s), "%.*s", BSTR_P(param)); snprintf(s, sizeof(s), "%.*s", BSTR_P(param));
if (!handle_scaler_opt(s)) if (!handle_scaler_opt(s, tscale))
r = M_OPT_INVALID; r = M_OPT_INVALID;
} }
if (r < 1) { if (r < 1) {
mp_info(log, "Available scalers:\n"); mp_info(log, "Available scalers:\n");
for (const char *const *filter = fixed_scale_filters; *filter; filter++) if (!tscale) {
mp_info(log, " %s\n", *filter); for (const char *const *filter = fixed_scale_filters; *filter; filter++)
for (int n = 0; mp_filter_kernels[n].name; n++) mp_info(log, " %s\n", *filter);
mp_info(log, " %s\n", mp_filter_kernels[n].name); }
for (int n = 0; mp_filter_kernels[n].name; n++) {
if (!tscale || !mp_filter_kernels[n].polar)
mp_info(log, " %s\n", mp_filter_kernels[n].name);
}
if (s[0]) if (s[0])
mp_fatal(log, "No scaler named '%s' found!\n", s); mp_fatal(log, "No scaler named '%s' found!\n", s);
} }

View File

@ -29,15 +29,15 @@ struct lut3d {
}; };
struct gl_video_opts { struct gl_video_opts {
char *scalers[2]; char *scalers[3];
char *dscaler; char *dscaler;
float gamma; float gamma;
int gamma_auto; int gamma_auto;
int target_prim; int target_prim;
int target_trc; int target_trc;
float scaler_params[2][2]; float scaler_params[3][2];
float scaler_radius[2]; float scaler_radius[3];
float scaler_antiring[2]; float scaler_antiring[3];
int linear_scaling; int linear_scaling;
int fancy_downscaling; int fancy_downscaling;
int sigmoid_upscaling; int sigmoid_upscaling;
@ -55,8 +55,7 @@ struct gl_video_opts {
int chroma_location; int chroma_location;
int use_rectangle; int use_rectangle;
struct m_color background; struct m_color background;
int smoothmotion; int interpolation;
float smoothmotion_threshold;
}; };
extern const struct m_sub_options gl_video_conf; extern const struct m_sub_options gl_video_conf;
@ -67,7 +66,8 @@ struct gl_video;
struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct osd_state *osd); struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct osd_state *osd);
void gl_video_uninit(struct gl_video *p); void gl_video_uninit(struct gl_video *p);
void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts); void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts,
int *queue_size);
bool gl_video_check_format(struct gl_video *p, int mp_format); bool gl_video_check_format(struct gl_video *p, int mp_format);
void gl_video_config(struct gl_video *p, struct mp_image_params *params); 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_output_depth(struct gl_video *p, int r, int g, int b);

View File

@ -315,8 +315,9 @@ static bool reparse_cmdline(struct gl_priv *p, char *args)
if (r >= 0) { if (r >= 0) {
mpgl_lock(p->glctx); mpgl_lock(p->glctx);
gl_video_set_options(p->renderer, opts->renderer_opts); int queue = 0;
vo_set_flip_queue_params(p->vo, 0, opts->renderer_opts->smoothmotion); gl_video_set_options(p->renderer, opts->renderer_opts, &queue);
vo_set_flip_queue_params(p->vo, queue, opts->renderer_opts->interpolation);
p->vo->want_redraw = true; p->vo->want_redraw = true;
mpgl_unlock(p->glctx); mpgl_unlock(p->glctx);
} }
@ -459,8 +460,9 @@ static int preinit(struct vo *vo)
goto err_out; goto err_out;
gl_video_set_output_depth(p->renderer, p->glctx->depth_r, p->glctx->depth_g, gl_video_set_output_depth(p->renderer, p->glctx->depth_r, p->glctx->depth_g,
p->glctx->depth_b); p->glctx->depth_b);
gl_video_set_options(p->renderer, p->renderer_opts); int queue = 0;
vo_set_flip_queue_params(vo, 0, p->renderer_opts->smoothmotion); gl_video_set_options(p->renderer, p->renderer_opts, &queue);
vo_set_flip_queue_params(p->vo, queue, p->renderer_opts->interpolation);
p->cms = gl_lcms_init(p, vo->log, vo->global); p->cms = gl_lcms_init(p, vo->log, vo->global);
if (!p->cms) if (!p->cms)

View File

@ -306,7 +306,7 @@ int mpv_opengl_cb_render(struct mpv_opengl_cb_context *ctx, int fbo, int vp[4])
struct vo_priv *p = vo ? vo->priv : NULL; struct vo_priv *p = vo ? vo->priv : NULL;
struct vo_priv *opts = ctx->new_opts ? ctx->new_opts : p; struct vo_priv *opts = ctx->new_opts ? ctx->new_opts : p;
if (opts) { if (opts) {
gl_video_set_options(ctx->renderer, opts->renderer_opts); gl_video_set_options(ctx->renderer, opts->renderer_opts, NULL);
ctx->gl->debug_context = opts->use_gl_debug; ctx->gl->debug_context = opts->use_gl_debug;
gl_video_set_debug(ctx->renderer, opts->use_gl_debug); gl_video_set_debug(ctx->renderer, opts->use_gl_debug);
frame_queue_shrink(ctx, opts->frame_queue_size); frame_queue_shrink(ctx, opts->frame_queue_size);