mirror of https://github.com/mpv-player/mpv
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:
parent
6f3292813f
commit
44a78a2be2
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue