diff options
-rw-r--r-- | DOCS/man/vo.rst | 9 | ||||
-rw-r--r-- | video/out/gl_video.c | 203 | ||||
-rw-r--r-- | video/out/gl_video.h | 3 | ||||
-rw-r--r-- | video/out/vo_opengl.c | 15 | ||||
-rw-r--r-- | video/out/vo_opengl_cb.c | 2 |
5 files changed, 117 insertions, 115 deletions
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst index abf72f9b00..576c34e8e3 100644 --- a/DOCS/man/vo.rst +++ b/DOCS/man/vo.rst @@ -495,12 +495,11 @@ Available video output drivers are: 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 default is ``oversample``. + to get a list). The default is ``robidoux``. - Note that the maximum supported filter radius is currently 3, and that - using filters with larger radius may introduce issues when pausing or - framestepping, proportional to the radius used. It is recommended to - stick to a radius of 1 or 2. + Note that the maximum supported filter radius is currently 3, due to + limitations in the number of video textures that can be loaded + simultaneously. ``dscale-radius``, ``cscale-radius``, ``tscale-radius``, etc. Set filter parameters for ``dscale``, ``cscale`` and ``tscale``, diff --git a/video/out/gl_video.c b/video/out/gl_video.c index c856fbe0df..478cd2263b 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -64,7 +64,7 @@ static const char *const fixed_scale_filters[] = { NULL }; static const char *const fixed_tscale_filters[] = { - "oversample", + //"oversample", NULL }; @@ -130,8 +130,7 @@ struct scaler { struct fbosurface { struct fbotex fbotex; - int64_t pts; - double vpts; // used for synchronizing subtitles only + double pts; }; #define FBOSURFACES_MAX 10 @@ -340,7 +339,7 @@ const struct gl_video_opts gl_video_opts_def = { {{"bilinear", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // scale {{NULL, .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // dscale {{"bilinear", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // cscale - {{"oversample", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // tscale + {{"robidoux", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // tscale }, .alpha_mode = 2, .background = {0, 0, 0, 255}, @@ -359,7 +358,7 @@ const struct gl_video_opts gl_video_opts_hq_def = { {{"spline36", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // scale {{"mitchell", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // dscale {{"spline36", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // cscale - {{"oversample", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // tscale + {{"robidoux", .params={NAN, NAN}}, {.params = {NAN, NAN}}}, // tscale }, .alpha_mode = 2, .background = {0, 0, 0, 255}, @@ -523,8 +522,7 @@ void gl_video_set_debug(struct gl_video *p, bool enable) static void gl_video_reset_surfaces(struct gl_video *p) { for (int i = 0; i < FBOSURFACES_MAX; i++) { - p->surfaces[i].pts = 0; - p->surfaces[i].vpts = MP_NOPTS_VALUE; + p->surfaces[i].pts = MP_NOPTS_VALUE; } p->surface_idx = 0; p->surface_now = 0; @@ -1981,8 +1979,13 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts, // The main rendering function, takes care of everything up to and including // upscaling -static void pass_render_frame(struct gl_video *p) +static void pass_render_frame(struct gl_video *p, struct mp_image *img) { + if (img) { + gl_video_set_image(p, img); + gl_video_upload_image(p); + } + p->use_linear = p->opts.linear_scaling || p->opts.sigmoid_upscaling; p->use_indirect = false; // set to true as needed by pass_* pass_read_video(p); @@ -2068,49 +2071,68 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo, // First of all, figure out if we have a frame availble at all, and draw // it manually + reset the queue if not - if (!p->surfaces[p->surface_now].pts) { - pass_render_frame(p); + if (p->surfaces[p->surface_now].pts == MP_NOPTS_VALUE) { + pass_render_frame(p, t->frame); finish_pass_fbo(p, &p->surfaces[p->surface_now].fbotex, vp_w, vp_h, 0, FBOTEX_FUZZY); - p->surfaces[p->surface_now].pts = t ? t->pts : 0; - p->surfaces[p->surface_now].vpts = p->image.mpi->pts; + p->surfaces[p->surface_now].pts = p->image.mpi->pts; p->surface_idx = p->surface_now; } + // Find the right frame for this instant + if (t->frame && t->frame->pts != MP_NOPTS_VALUE) { + int next = fbosurface_wrap(p->surface_now + 1); + while (p->surfaces[next].pts != MP_NOPTS_VALUE && + p->surfaces[next].pts > p->surfaces[p->surface_now].pts && + p->surfaces[p->surface_now].pts < t->frame->pts) + { + p->surface_now = next; + next = fbosurface_wrap(next + 1); + } + } + // 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->scaler[3]; reinit_scaler(p, tscale, &p->opts.scaler[3], 1, tscale_sizes); - bool oversample = strcmp(tscale->conf.kernel.name, "oversample") == 0; int size; - if (oversample) { - size = 2; - } else { - assert(tscale->kernel && !tscale->kernel->polar); - size = ceil(tscale->kernel->size); - assert(size <= TEXUNIT_VIDEO_NUM); - } - int radius = size/2; + assert(tscale->kernel && !tscale->kernel->polar); + 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 new frames while there's room in the queue. Note that technically, + // this should be done before the step where we find the right frame, but + // it only barely matters at the very beginning of playback, and this way + // makes the code much more linear. 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_render_frame(p); - finish_pass_fbo(p, &p->surfaces[surface_dst].fbotex, - vp_w, vp_h, 0, FBOTEX_FUZZY); - p->surfaces[surface_dst].pts = t->pts; - p->surfaces[surface_dst].vpts = p->image.mpi->pts; - p->surface_idx = surface_dst; + for (int i = -1; i < t->num_future_frames; i++) { + // Avoid overwriting data we might still need + if (surface_dst == surface_bse - 1) + break; + + struct mp_image *f = i < 0 ? t->frame : t->future_frames[i]; + if (!f || f->pts == MP_NOPTS_VALUE) + continue; + + if (f->pts > p->surfaces[p->surface_idx].pts) { + MP_STATS(p, "new-pts"); + pass_render_frame(p, f); + finish_pass_fbo(p, &p->surfaces[surface_dst].fbotex, + vp_w, vp_h, 0, FBOTEX_FUZZY); + p->surfaces[surface_dst].pts = f->pts; + p->surface_idx = surface_dst; + surface_dst = fbosurface_wrap(surface_dst+1); + } } // Figure out whether the queue is "valid". A queue is invalid if the @@ -2121,7 +2143,9 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo, bool valid = true; for (int i = surface_bse, ii; valid && i != surface_end; i = ii) { ii = fbosurface_wrap(i+1); - if (!p->surfaces[i].pts || !p->surfaces[ii].pts) { + if (p->surfaces[i].pts == MP_NOPTS_VALUE || + p->surfaces[ii].pts == MP_NOPTS_VALUE) + { valid = false; } else if (p->surfaces[ii].pts < p->surfaces[i].pts) { valid = false; @@ -2130,81 +2154,63 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo, } // Update OSD PTS to synchronize subtitles with the displayed frame - if (t) { - double vpts_now = p->surfaces[surface_now].vpts, - vpts_nxt = p->surfaces[surface_nxt].vpts, - vpts_new = p->image.mpi->pts; - if (vpts_now != MP_NOPTS_VALUE && - vpts_nxt != MP_NOPTS_VALUE && - vpts_new != MP_NOPTS_VALUE) - { - // Round to nearest neighbour - double vpts_vsync = (t->next_vsync - t->pts)/1e6 + vpts_new; - p->osd_pts = fabs(vpts_vsync-vpts_now) < fabs(vpts_vsync-vpts_nxt) - ? vpts_now : vpts_nxt; - } - } + p->osd_pts = p->surfaces[surface_now].pts; // Finally, draw the right mix of frames to the screen. - if (!t || !valid) { + if (!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 pts_now = p->surfaces[surface_now].pts, - pts_nxt = p->surfaces[surface_nxt].pts; - double fscale = pts_nxt - pts_now, mix; - if (oversample) { - double vsync_interval = t->next_vsync - t->prev_vsync, - threshold = tscale->conf.kernel.params[0]; - threshold = isnan(threshold) ? 0.0 : threshold; - mix = (pts_nxt - t->next_vsync) / vsync_interval; - mix = mix <= 0 + threshold ? 0 : mix; - mix = mix >= 1 - threshold ? 1 : mix; - mix = 1 - mix; - gl_sc_uniform_f(p->sc, "inter_coeff", mix); - GLSL(vec4 color = mix(texture(texture0, texcoord0), - texture(texture1, texcoord1), - inter_coeff);) - } else { - mix = (t->next_vsync - pts_now) / fscale; - gl_sc_uniform_f(p->sc, "fcoord", mix); - pass_sample_separated_gen(p, tscale, 0, 0); + double pts_now = p->surfaces[surface_now].pts, + pts_nxt = p->surfaces[surface_nxt].pts; + + double mix = (t->vsync_offset / 1e6) / (pts_nxt - pts_now); + // The scaler code always wants the fcoord to be between 0 and 1, + // so we try to adjust by using the previous set of N frames instead + // (which requires some extra checking to make sure it's valid) + if (mix < 0.0) { + int prev = fbosurface_wrap(surface_bse - 1); + if (p->surfaces[prev].pts != MP_NOPTS_VALUE && + p->surfaces[prev].pts < p->surfaces[surface_bse].pts) + { + mix += 1.0; + surface_bse = prev; + } else { + mix = 0.0; // at least don't blow up, this should only + // ever happen at the start of playback + } } + + // Non-oversample case + gl_sc_uniform_f(p->sc, "fcoord", mix); + pass_sample_separated_gen(p, tscale, 0, 0); + + // Load all the required frames for (int i = 0; i < size; i++) { pass_load_fbotex(p, &p->surfaces[fbosurface_wrap(surface_bse+i)].fbotex, i, vp_w, vp_h); } + MP_STATS(p, "frame-mix"); - MP_DBG(p, "inter frame ppts: %lld, pts: %lld, vsync: %lld, mix: %f\n", - (long long)pts_now, (long long)pts_nxt, - (long long)t->next_vsync, mix); + MP_DBG(p, "inter frame pts: %lld, vsync: %lld, mix: %f\n", + (long long)t->pts, (long long)t->next_vsync, mix); 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; - } - } } // (fbo==0 makes BindFramebuffer select the screen backbuffer) -void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t) +void gl_video_render_frame(struct gl_video *p, struct mp_image *mpi, int fbo, + struct frame_timing *t) { GL *gl = p->gl; struct video_image *vimg = &p->image; gl->BindFramebuffer(GL_FRAMEBUFFER, fbo); - if (!vimg->mpi || p->dst_rect.x0 > 0 || p->dst_rect.y0 > 0 || + if ((!mpi && !vimg->mpi) || p->dst_rect.x0 > 0 || p->dst_rect.y0 > 0 || p->dst_rect.x1 < p->vp_w || p->dst_rect.y1 < abs(p->vp_h)) { struct m_color c = p->opts.background; @@ -2212,22 +2218,18 @@ void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t) gl->Clear(GL_COLOR_BUFFER_BIT); } - if (vimg->mpi) { - gl_video_upload_image(p); - - gl_sc_set_vao(p->sc, &p->vao); - - if (p->opts.interpolation) { - gl_video_interpolate_frame(p, fbo, t); - } else { - // Skip interpolation if there's nothing to be done - pass_render_frame(p); - pass_draw_to_screen(p, fbo); - } + gl_sc_set_vao(p->sc, &p->vao); - debug_check_gl(p, "after video rendering"); + if (p->opts.interpolation && t) { + gl_video_interpolate_frame(p, fbo, t); + } else { + // Skip interpolation if there's nothing to be done + pass_render_frame(p, mpi); + pass_draw_to_screen(p, fbo); } + debug_check_gl(p, "after video rendering"); + gl->BindFramebuffer(GL_FRAMEBUFFER, fbo); if (p->osd) { @@ -2297,9 +2299,12 @@ void gl_video_set_image(struct gl_video *p, struct mp_image *mpi) struct video_image *vimg = &p->image; talloc_free(vimg->mpi); - vimg->mpi = mpi; + vimg->mpi = mp_image_new_ref(mpi); vimg->needs_upload = true; + if (!vimg->mpi) + abort(); + p->osd_pts = mpi->pts; } @@ -2793,16 +2798,14 @@ void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts, } // 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. + // the radius, the earlier we need to queue frames. if (queue_size && p->opts.interpolation) { const struct filter_kernel *kernel = mp_find_filter_kernel(p->opts.scaler[3].kernel.name); if (kernel) { double radius = kernel->f.radius; radius = radius > 0 ? radius : p->opts.scaler[3].radius; - *queue_size = 50e3 * ceil(radius); + *queue_size = 1 + ceil(radius); } } diff --git a/video/out/gl_video.h b/video/out/gl_video.h index 1a56d6c1fc..e3ce40b606 100644 --- a/video/out/gl_video.h +++ b/video/out/gl_video.h @@ -88,7 +88,8 @@ void gl_video_config(struct gl_video *p, struct mp_image_params *params); void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b); void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d); void gl_video_set_image(struct gl_video *p, struct mp_image *img); -void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t); +void gl_video_render_frame(struct gl_video *p, struct mp_image *img, int fbo, + struct frame_timing *t); void gl_video_resize(struct gl_video *p, int vp_w, int vp_h, struct mp_rect *src, struct mp_rect *dst, struct mp_osd_res *osd); diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 981e73ffcb..2d3ab5ba93 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -170,14 +170,11 @@ static void draw_image_timed(struct vo *vo, mp_image_t *mpi, struct gl_priv *p = vo->priv; GL *gl = p->gl; - if (mpi) - gl_video_set_image(p->renderer, mpi); - if (p->glctx->start_frame && !p->glctx->start_frame(p->glctx)) return; p->frame_started = true; - gl_video_render_frame(p->renderer, 0, t); + gl_video_render_frame(p->renderer, mpi, 0, t); // The playloop calls this last before waiting some time until it decides // to call flip_page(). Tell OpenGL to start execution of the GPU commands @@ -186,6 +183,8 @@ static void draw_image_timed(struct vo *vo, mp_image_t *mpi, if (p->use_glFinish) gl->Finish(); + + talloc_free(mpi); } static void draw_image(struct vo *vo, mp_image_t *mpi) @@ -302,9 +301,9 @@ static bool reparse_cmdline(struct gl_priv *p, char *args) } if (r >= 0) { - int queue = 0; + int queue = 1; gl_video_set_options(p->renderer, opts->renderer_opts, &queue); - vo_set_queue_params(p->vo, queue, opts->renderer_opts->interpolation, 1); + vo_set_queue_params(p->vo, 0, opts->renderer_opts->interpolation, queue); p->vo->want_redraw = true; } @@ -359,7 +358,7 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_REDRAW_FRAME: if (!(p->glctx->start_frame && !p->glctx->start_frame(p->glctx))) { p->frame_started = true; - gl_video_render_frame(p->renderer, 0, NULL); + gl_video_render_frame(p->renderer, NULL, 0, NULL); } return true; case VOCTRL_SET_COMMAND_LINE: { @@ -443,7 +442,7 @@ static int preinit(struct vo *vo) p->glctx->depth_b); int queue = 0; gl_video_set_options(p->renderer, p->renderer_opts, &queue); - vo_set_queue_params(p->vo, queue, p->renderer_opts->interpolation, 0); + vo_set_queue_params(p->vo, 0, p->renderer_opts->interpolation, queue); 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 efc2991ba7..96a312f98c 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_opengl_cb.c @@ -372,7 +372,7 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h) if (mpi) gl_video_set_image(ctx->renderer, mpi); - gl_video_render_frame(ctx->renderer, fbo, timing.pts ? &timing : NULL); + gl_video_render_frame(ctx->renderer, mpi, fbo, timing.pts ? &timing : NULL); gl_video_unset_gl_state(ctx->renderer); |