summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiklas Haas <git@nand.wakku.to>2015-03-13 19:30:31 +0100
committerNiklas Haas <git@nand.wakku.to>2015-03-15 18:01:39 +0100
commit44a78a2be20ef3affac7e6f3aa98c0742cf019a9 (patch)
tree845c5e4351cad7a864fee41e80385be926024249
parent6f3292813f27c420e1e2a2382aaaf2193ca8491e (diff)
downloadmpv-44a78a2be20ef3affac7e6f3aa98c0742cf019a9.tar.bz2
mpv-44a78a2be20ef3affac7e6f3aa98c0742cf019a9.tar.xz
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.
-rw-r--r--DOCS/man/vo.rst72
-rw-r--r--video/out/gl_video.c221
-rw-r--r--video/out/gl_video.h14
-rw-r--r--video/out/vo_opengl.c10
-rw-r--r--video/out/vo_opengl_cb.c2
5 files changed, 189 insertions, 130 deletions
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index a9a8e6db95..d76c95f987 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -426,11 +426,29 @@ Available video output drivers are:
debug OpenGL context (which does nothing with current graphics drivers
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>``
Interval in displayed frames between two buffer swaps.
1 is equivalent to enable VSYNC, 0 to disable VSYNC. Defaults to 1 if
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>``
As ``scale``, but for interpolating chroma information. If the image
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
``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``
Scale in linear light. This is automatically enabled if
``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.
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``
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 68611acdaa..5dc362335e 100644
--- a/video/out/gl_video.c
+++ b/video/out/gl_video.c
@@ -49,7 +49,7 @@
// Other texture units are reserved for specific purposes
#define TEXUNIT_SCALERS TEXUNIT_VIDEO_NUM
-#define TEXUNIT_3DLUT (TEXUNIT_SCALERS+2)
+#define TEXUNIT_3DLUT (TEXUNIT_SCALERS+3)
#define TEXUNIT_DITHER (TEXUNIT_3DLUT+1)
// 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
-// 2 & 6 are special-cased, the rest can be generated with WEIGHTS_N().
int filter_sizes[] =
{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 {
float x, y;
@@ -129,7 +129,7 @@ struct fbosurface {
int64_t pts;
};
-#define FBOSURFACES_MAX 6
+#define FBOSURFACES_MAX 10
struct src_tex {
GLuint gl_tex;
@@ -182,12 +182,12 @@ struct gl_video {
struct fbotex chroma_merge_fbo;
struct fbosurface surfaces[FBOSURFACES_MAX];
- size_t surface_idx;
- size_t surface_now;
+ int surface_idx;
+ int surface_now;
bool is_interpolated;
- // state for luma (0) and chroma (1) scalers
- struct scaler scalers[2];
+ // state for luma (0), chroma (1) and temporal (2) scalers
+ struct scaler scalers[3];
struct mp_csp_equalizer video_eq;
@@ -320,10 +320,10 @@ const struct gl_video_opts gl_video_opts_def = {
.fbo_format = GL_RGBA,
.sigmoid_center = 0.75,
.sigmoid_slope = 6.5,
- .scalers = { "bilinear", "bilinear" },
+ .scalers = { "bilinear", "bilinear", "mitchell" },
.dscaler = "bilinear",
- .scaler_params = {{NAN, NAN}, {NAN, NAN}},
- .scaler_radius = {3, 3},
+ .scaler_params = {{NAN, NAN}, {NAN, NAN}, {NAN, NAN}},
+ .scaler_radius = {3, 3, 3},
.alpha_mode = 2,
.background = {0, 0, 0, 255},
.gamma = 1.0f,
@@ -338,10 +338,10 @@ const struct gl_video_opts gl_video_opts_hq_def = {
.sigmoid_center = 0.75,
.sigmoid_slope = 6.5,
.sigmoid_upscaling = 1,
- .scalers = { "spline36", "bilinear" },
+ .scalers = { "spline36", "bilinear", "mitchell" },
.dscaler = "mitchell",
- .scaler_params = {{NAN, NAN}, {NAN, NAN}},
- .scaler_radius = {3, 3},
+ .scaler_params = {{NAN, NAN}, {NAN, NAN}, {NAN, NAN}},
+ .scaler_radius = {3, 3, 3},
.alpha_mode = 2,
.background = {0, 0, 0, 255},
.gamma = 1.0f,
@@ -372,15 +372,20 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("pbo", pbo, 0),
OPT_STRING_VALIDATE("scale", scalers[0], 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_FLOAT("scale-param1", scaler_params[0][0], 0),
OPT_FLOAT("scale-param2", scaler_params[0][1], 0),
OPT_FLOAT("cscale-param1", scaler_params[1][0], 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("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("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("linear-scaling", linear_scaling, 0),
OPT_FLAG("fancy-downscaling", fancy_downscaling, 0),
@@ -416,13 +421,12 @@ 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_FLAG("interpolation", interpolation, 0),
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"),
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-down", "scale-down"),
@@ -435,6 +439,7 @@ const struct m_sub_options gl_video_conf = {
OPT_REPLACED("cradius", "cscale-radius"),
OPT_REPLACED("cantiring", "cscale-antiring"),
OPT_REPLACED("srgb", "target-prim=srgb:target-trc=srgb"),
+ OPT_REPLACED("smoothmotion", "interpolation"),
{0}
},
@@ -489,9 +494,10 @@ static void gl_video_reset_surfaces(struct gl_video *p)
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)
@@ -517,7 +523,7 @@ static void uninit_rendering(struct gl_video *p)
{
GL *gl = p->gl;
- for (int n = 0; n < 2; n++)
+ for (int n = 0; n < 3; n++)
uninit_scaler(p, n);
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,
- double scale_factor)
+ double scale_factor, int sizes[])
{
GL *gl = p->gl;
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)
scaler->kernel->radius = p->opts.scaler_radius[scaler->index];
- scaler->insufficient = !mp_init_filter(scaler->kernel, filter_sizes,
- scale_factor);
+ scaler->insufficient = !mp_init_filter(scaler->kernel, sizes, scale_factor);
if (scaler->kernel->polar) {
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
-// 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,
int d_x, int d_y)
{
int N = scaler->kernel->size;
bool use_ar = scaler->antiring > 0;
+ bool planar = d_x == 0 && d_y == 0;
GLSL(vec4 color = vec4(0.0);)
GLSLF("{\n");
- GLSLF("vec2 dir = vec2(%d, %d);\n", d_x, d_y);
- GLSL(vec2 pt = (vec2(1.0) / sample_size) * dir;)
- 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);
+ if (!planar) {
+ GLSLF("vec2 dir = vec2(%d, %d);\n", d_x, d_y);
+ GLSL(vec2 pt = (vec2(1.0) / sample_size) * dir;)
+ 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;)
if (use_ar) {
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);
GLSLF("// scaler samples\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);
if (use_ar && (n == N/2-1 || n == N/2)) {
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];
GLSLF("color = p + t * %f;\n", param);
GLSLF("}\n");
-
}
// 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)
{
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
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,
vp_h = p->dst_rect.y1 - p->dst_rect.y0,
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
// 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;
}
+ // 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
- size_t surface_dst = fbosurface_next(p->surface_idx);
- if (t && surface_dst != p->surface_now &&
+ int surface_dst = fbosurface_wrap(p->surface_idx+1);
+ if (t && surface_dst != surface_bse &&
p->surfaces[p->surface_idx].pts < t->pts) {
MP_STATS(p, "new-pts");
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;
}
+ // 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.
- pass_load_fbotex(p, &p->surfaces[p->surface_now].fbotex, 0, vp_w, vp_h);
- if (!t || p->surfaces[surface_nxt].pts < p->surfaces[p->surface_now].pts) {
- // No next frame available (eg. start of playback, after reconfigure
- // or end of file, so just draw the current frame instead of blending.
- // Also occurs when no timing information is available (eg. paused)
+ if (!t || !valid) {
+ // surface_now is guaranteed to be valid, so we can safely use it.
+ pass_load_fbotex(p, &p->surfaces[surface_now].fbotex, 0, vp_w, vp_h);
GLSL(vec4 color = texture(texture0, texcoord0);)
p->is_interpolated = false;
} else {
- int64_t next_pts = p->surfaces[surface_nxt].pts,
- vsync_interval = t->next_vsync - t->prev_vsync;
- double inter_coeff = (double)(next_pts - t->next_vsync) / vsync_interval,
- threshold = p->opts.smoothmotion_threshold;
- inter_coeff = inter_coeff <= 0.0 + threshold ? 0.0 : inter_coeff;
- 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;
+ int64_t pts_now = p->surfaces[surface_now].pts,
+ pts_nxt = p->surfaces[surface_nxt].pts;
+ double fscale = pts_nxt - pts_now;
+ double fcoord = (t->next_vsync - pts_now) / fscale;
+ gl_sc_uniform_f(p->sc, "fcoord", fcoord);
MP_STATS(p, "frame-mix");
MP_DBG(p, "inter frame ppts: %lld, pts: %lld, vsync: %lld, mix: %f\n",
- (long long)p->surfaces[p->surface_now].pts,
- (long long)p->surfaces[surface_nxt].pts,
- (long long)t->next_vsync, inter_coeff);
-
- 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;
+ (long long)pts_now, (long long)pts_nxt,
+ (long long)t->next_vsync, fcoord);
+
+ 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_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)
@@ -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);
- if (p->opts.smoothmotion) {
+ if (p->opts.interpolation) {
gl_video_interpolate_frame(p, fbo, t);
} else {
+ // Skip interpolation if there's nothing to be done
pass_draw_frame(p);
pass_draw_to_screen(p, fbo);
}
@@ -1982,9 +2030,9 @@ static void check_gl_features(struct gl_video *p)
p->use_lut_3d = false;
disabled[n_disabled++] = "color management (FBO)";
}
- if (p->opts.smoothmotion && !test_fbo(p, &have_fbo)) {
- p->opts.smoothmotion = false;
- disabled[n_disabled++] = "smoothmotion (FBO)";
+ if (p->opts.interpolation && !test_fbo(p, &have_fbo)) {
+ p->opts.interpolation = false;
+ disabled[n_disabled++] = "interpolation (FBO)";
}
if (gl->es && p->opts.pbo) {
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 = {
{ .index = 0, .name = "bilinear" },
{ .index = 1, .name = "bilinear" },
+ { .index = 2, .name = "mitchell" },
},
.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;
}
-// Get static string for scaler shader.
-static const char *handle_scaler_opt(const char *name)
+// Get static string for scaler shader. If "tscale" is set to true, the
+// scaler must be a separable convolution filter.
+static const char *handle_scaler_opt(const char *name, bool tscale)
{
if (name && name[0]) {
const struct filter_kernel *kernel = mp_find_filter_kernel(name);
- if (kernel)
- return kernel->name;
+ if (kernel && (!tscale || !kernel->polar))
+ return kernel->name;
for (const char *const *filter = fixed_scale_filters; *filter; filter++) {
- if (strcmp(*filter, name) == 0)
+ if (strcmp(*filter, name) == 0 && !tscale)
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.
// 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;
- for (int n = 0; n < 2; n++)
- p->opts.scalers[n] = (char *)handle_scaler_opt(p->opts.scalers[n]);
- p->opts.dscaler = (char *)handle_scaler_opt(p->opts.dscaler);
+ for (int n = 0; n < 3; 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, 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);
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};
int r = 1;
+ bool tscale = bstr_equals0(name, "tscale");
if (bstr_equals0(param, "help")) {
r = M_OPT_EXIT - 1;
} else {
snprintf(s, sizeof(s), "%.*s", BSTR_P(param));
- if (!handle_scaler_opt(s))
+ if (!handle_scaler_opt(s, tscale))
r = M_OPT_INVALID;
}
if (r < 1) {
mp_info(log, "Available scalers:\n");
- for (const char *const *filter = fixed_scale_filters; *filter; filter++)
- mp_info(log, " %s\n", *filter);
- for (int n = 0; mp_filter_kernels[n].name; n++)
- mp_info(log, " %s\n", mp_filter_kernels[n].name);
+ if (!tscale) {
+ for (const char *const *filter = fixed_scale_filters; *filter; filter++)
+ mp_info(log, " %s\n", *filter);
+ }
+ 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])
mp_fatal(log, "No scaler named '%s' found!\n", s);
}
diff --git a/video/out/gl_video.h b/video/out/gl_video.h
index 9c70e9ba82..8ec59895a1 100644
--- a/video/out/gl_video.h
+++ b/video/out/gl_video.h
@@ -29,15 +29,15 @@ struct lut3d {
};
struct gl_video_opts {
- char *scalers[2];
+ char *scalers[3];
char *dscaler;
float gamma;
int gamma_auto;
int target_prim;
int target_trc;
- float scaler_params[2][2];
- float scaler_radius[2];
- float scaler_antiring[2];
+ float scaler_params[3][2];
+ float scaler_radius[3];
+ float scaler_antiring[3];
int linear_scaling;
int fancy_downscaling;
int sigmoid_upscaling;
@@ -55,8 +55,7 @@ struct gl_video_opts {
int chroma_location;
int use_rectangle;
struct m_color background;
- int smoothmotion;
- float smoothmotion_threshold;
+ int interpolation;
};
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);
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);
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);
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
index aa3dd0c9e0..3abd1d3320 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -315,8 +315,9 @@ static bool reparse_cmdline(struct gl_priv *p, char *args)
if (r >= 0) {
mpgl_lock(p->glctx);
- gl_video_set_options(p->renderer, opts->renderer_opts);
- vo_set_flip_queue_params(p->vo, 0, opts->renderer_opts->smoothmotion);
+ int queue = 0;
+ 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;
mpgl_unlock(p->glctx);
}
@@ -459,8 +460,9 @@ static int preinit(struct vo *vo)
goto err_out;
gl_video_set_output_depth(p->renderer, p->glctx->depth_r, p->glctx->depth_g,
p->glctx->depth_b);
- gl_video_set_options(p->renderer, p->renderer_opts);
- vo_set_flip_queue_params(vo, 0, p->renderer_opts->smoothmotion);
+ int queue = 0;
+ 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);
if (!p->cms)
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index e20be8dd67..9b7f650eb2 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -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 *opts = ctx->new_opts ? ctx->new_opts : p;
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;
gl_video_set_debug(ctx->renderer, opts->use_gl_debug);
frame_queue_shrink(ctx, opts->frame_queue_size);