diff options
Diffstat (limited to 'video/out/vo.c')
-rw-r--r-- | video/out/vo.c | 523 |
1 files changed, 308 insertions, 215 deletions
diff --git a/video/out/vo.c b/video/out/vo.c index e5f8752f07..db29690950 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -15,18 +15,17 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ +#include <assert.h> +#include <math.h> +#include <stdatomic.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <assert.h> -#include <stdbool.h> -#include <pthread.h> -#include <math.h> #include "mpv_talloc.h" #include "config.h" -#include "osdep/atomic.h" #include "osdep/timer.h" #include "osdep/threads.h" #include "misc/dispatch.h" @@ -40,6 +39,7 @@ #include "options/m_config.h" #include "common/msg.h" #include "common/global.h" +#include "common/stats.h" #include "video/hwdec.h" #include "video/mp_image.h" #include "sub/osd.h" @@ -51,6 +51,7 @@ extern const struct vo_driver video_out_x11; extern const struct vo_driver video_out_vdpau; extern const struct vo_driver video_out_xv; extern const struct vo_driver video_out_gpu; +extern const struct vo_driver video_out_gpu_next; extern const struct vo_driver video_out_libmpv; extern const struct vo_driver video_out_null; extern const struct vo_driver video_out_image; @@ -60,17 +61,19 @@ extern const struct vo_driver video_out_drm; extern const struct vo_driver video_out_direct3d; extern const struct vo_driver video_out_sdl; extern const struct vo_driver video_out_vaapi; +extern const struct vo_driver video_out_dmabuf_wayland; extern const struct vo_driver video_out_wlshm; -extern const struct vo_driver video_out_rpi; extern const struct vo_driver video_out_tct; +extern const struct vo_driver video_out_sixel; +extern const struct vo_driver video_out_kitty; -const struct vo_driver *const video_out_drivers[] = +static const struct vo_driver *const video_out_drivers[] = { - &video_out_libmpv, #if HAVE_ANDROID &video_out_mediacodec_embed, #endif &video_out_gpu, + &video_out_gpu_next, #if HAVE_VDPAU &video_out_vdpau, #endif @@ -86,12 +89,16 @@ const struct vo_driver *const video_out_drivers[] = #if HAVE_SDL2_VIDEO &video_out_sdl, #endif +#if HAVE_DMABUF_WAYLAND + &video_out_dmabuf_wayland, +#endif #if HAVE_VAAPI_X11 && HAVE_GPL &video_out_vaapi, #endif #if HAVE_X11 &video_out_x11, #endif + &video_out_libmpv, &video_out_null, // should not be auto-selected &video_out_image, @@ -102,21 +109,21 @@ const struct vo_driver *const video_out_drivers[] = #if HAVE_DRM &video_out_drm, #endif -#if HAVE_RPI_MMAL - &video_out_rpi, +#if HAVE_SIXEL + &video_out_sixel, #endif + &video_out_kitty, &video_out_lavc, - NULL }; struct vo_internal { - pthread_t thread; + mp_thread thread; struct mp_dispatch_queue *dispatch; struct dr_helper *dr_helper; // --- The following fields are protected by lock - pthread_mutex_t lock; - pthread_cond_t wakeup; + mp_mutex lock; + mp_cond wakeup; bool need_wakeup; bool terminate; @@ -127,17 +134,18 @@ struct vo_internal { bool want_redraw; // redraw request from VO to player bool send_reset; // send VOCTRL_RESET bool paused; + bool wakeup_on_done; int queued_events; // event mask for the user int internal_events; // event mask for us - int64_t nominal_vsync_interval; + double nominal_vsync_interval; - int64_t vsync_interval; + double vsync_interval; int64_t *vsync_samples; int num_vsync_samples; int64_t num_total_vsync_samples; int64_t prev_vsync; - int64_t base_vsync; + double base_vsync; int drop_point; double estimated_vsync_interval; double estimated_vsync_jitter; @@ -162,16 +170,18 @@ struct vo_internal { double display_fps; double reported_display_fps; + + struct stats_ctx *stats; }; extern const struct m_sub_options gl_video_conf; static void forget_frames(struct vo *vo); -static void *vo_thread(void *ptr); +static MP_THREAD_VOID vo_thread(void *ptr); static bool get_desc(struct m_obj_desc *dst, int index) { - if (index >= MP_ARRAY_SIZE(video_out_drivers) - 1) + if (index >= MP_ARRAY_SIZE(video_out_drivers)) return false; const struct vo_driver *vo = video_out_drivers[index]; *dst = (struct m_obj_desc) { @@ -199,7 +209,6 @@ const struct m_obj_list vo_obj_list = { {"opengl-cb", "libmpv"}, {0} }, - .allow_unknown_entries = true, .allow_trailer = true, .disallow_positional_parameters = true, .use_global_options = true, @@ -216,9 +225,9 @@ static void read_opts(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); - in->timing_offset = (uint64_t)(vo->opts->timing_offset * 1e6); - pthread_mutex_unlock(&in->lock); + mp_mutex_lock(&in->lock); + in->timing_offset = (uint64_t)(MP_TIME_S_TO_NS(vo->opts->timing_offset)); + mp_mutex_unlock(&in->lock); } static void update_opts(void *p) @@ -227,7 +236,6 @@ static void update_opts(void *p) if (m_config_cache_update(vo->opts_cache)) { read_opts(vo); - if (vo->driver->control) { vo->driver->control(vo, VOCTRL_VO_OPTS_CHANGED, NULL); // "Legacy" update of video position related options. @@ -235,18 +243,6 @@ static void update_opts(void *p) vo->driver->control(vo, VOCTRL_SET_PANSCAN, NULL); } } - - if (vo->gl_opts_cache && m_config_cache_update(vo->gl_opts_cache)) { - // "Legacy" update of video GL renderer related options. - if (vo->driver->control) - vo->driver->control(vo, VOCTRL_UPDATE_RENDER_OPTS, NULL); - } - - if (m_config_cache_update(vo->eq_opts_cache)) { - // "Legacy" update of video equalizer related options. - if (vo->driver->control) - vo->driver->control(vo, VOCTRL_SET_EQUALIZER, NULL); - } } // Does not include thread- and VO uninit. @@ -258,9 +254,10 @@ static void dealloc_vo(struct vo *vo) talloc_free(vo->opts_cache); talloc_free(vo->gl_opts_cache); talloc_free(vo->eq_opts_cache); + mp_mutex_destroy(&vo->params_mutex); - pthread_mutex_destroy(&vo->in->lock); - pthread_cond_destroy(&vo->in->wakeup); + mp_mutex_destroy(&vo->in->lock); + mp_cond_destroy(&vo->in->wakeup); talloc_free(vo); } @@ -289,15 +286,17 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, .probing = probing, .in = talloc(vo, struct vo_internal), }; + mp_mutex_init(&vo->params_mutex); talloc_steal(vo, log); *vo->in = (struct vo_internal) { .dispatch = mp_dispatch_create(vo), .req_frames = 1, .estimated_vsync_jitter = -1, + .stats = stats_ctx_create(vo, global, "vo"), }; mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo); - pthread_mutex_init(&vo->in->lock, NULL); - pthread_cond_init(&vo->in->wakeup, NULL); + mp_mutex_init(&vo->in->lock); + mp_cond_init(&vo->in->wakeup); vo->opts_cache = m_config_cache_alloc(NULL, global, &vo_sub_opts); vo->opts = vo->opts_cache->opts; @@ -306,12 +305,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, update_opts, vo); vo->gl_opts_cache = m_config_cache_alloc(NULL, global, &gl_video_conf); - m_config_cache_set_dispatch_change_cb(vo->gl_opts_cache, vo->in->dispatch, - update_opts, vo); - vo->eq_opts_cache = m_config_cache_alloc(NULL, global, &mp_csp_equalizer_conf); - m_config_cache_set_dispatch_change_cb(vo->eq_opts_cache, vo->in->dispatch, - update_opts, vo); mp_input_set_mouse_transform(vo->input_ctx, NULL, NULL); if (vo->driver->encode != !!vo->encode_lavc_ctx) @@ -320,10 +314,10 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, if (!vo->priv) goto error; - if (pthread_create(&vo->in->thread, NULL, vo_thread, vo)) + if (mp_thread_create(&vo->in->thread, vo_thread, vo)) goto error; if (mp_rendezvous(vo, 0) < 0) { // init barrier - pthread_join(vo->in->thread, NULL); + mp_thread_join(vo->in->thread); goto error; } return vo; @@ -353,7 +347,7 @@ struct vo *init_best_video_out(struct mpv_global *global, struct vo_extra *ex) } autoprobe: // now try the rest... - for (int i = 0; video_out_drivers[i]; i++) { + for (int i = 0; i < MP_ARRAY_SIZE(video_out_drivers); i++) { const struct vo_driver *driver = video_out_drivers[i]; if (driver == &video_out_null) break; @@ -377,7 +371,7 @@ void vo_destroy(struct vo *vo) { struct vo_internal *in = vo->in; mp_dispatch_run(in->dispatch, terminate_vo, vo); - pthread_join(vo->in->thread, NULL); + mp_thread_join(vo->in->thread); dealloc_vo(vo); } @@ -398,7 +392,7 @@ static void reset_vsync_timings(struct vo *vo) in->num_successive_vsyncs = 0; } -static double vsync_stddef(struct vo *vo, int64_t ref_vsync) +static double vsync_stddef(struct vo *vo, double ref_vsync) { struct vo_internal *in = vo->in; double jitter = 0; @@ -409,7 +403,8 @@ static double vsync_stddef(struct vo *vo, int64_t ref_vsync) return sqrt(jitter / in->num_vsync_samples); } -#define MAX_VSYNC_SAMPLES 200 +#define MAX_VSYNC_SAMPLES 1000 +#define DELAY_VSYNC_SAMPLES 10 // Check if we should switch to measured average display FPS if it seems // "better" then the system-reported one. (Note that small differences are @@ -420,8 +415,8 @@ static void check_estimated_display_fps(struct vo *vo) bool use_estimated = false; if (in->num_total_vsync_samples >= MAX_VSYNC_SAMPLES / 2 && - in->estimated_vsync_interval <= 1e6 / 20.0 && - in->estimated_vsync_interval >= 1e6 / 99.0) + in->estimated_vsync_interval <= 1e9 / 20.0 && + in->estimated_vsync_interval >= 1e9 / 400.0) { for (int n = 0; n < in->num_vsync_samples; n++) { if (fabs(in->vsync_samples[n] - in->estimated_vsync_interval) @@ -434,16 +429,16 @@ static void check_estimated_display_fps(struct vo *vo) use_estimated = true; done: ; } - if (use_estimated == (in->vsync_interval == in->nominal_vsync_interval)) { + if (use_estimated == (fabs(in->vsync_interval - in->nominal_vsync_interval) < 1e9)) { if (use_estimated) { - MP_VERBOSE(vo, "adjusting display FPS to a value closer to %.3f Hz\n", - 1e6 / in->estimated_vsync_interval); + MP_TRACE(vo, "adjusting display FPS to a value closer to %.3f Hz\n", + 1e9 / in->estimated_vsync_interval); } else { - MP_VERBOSE(vo, "switching back to assuming display fps = %.3f Hz\n", - 1e6 / in->nominal_vsync_interval); + MP_TRACE(vo, "switching back to assuming display fps = %.3f Hz\n", + 1e9 / in->nominal_vsync_interval); } } - in->vsync_interval = use_estimated ? (int64_t)in->estimated_vsync_interval + in->vsync_interval = use_estimated ? in->estimated_vsync_interval : in->nominal_vsync_interval; } @@ -454,7 +449,7 @@ static void vsync_skip_detection(struct vo *vo) struct vo_internal *in = vo->in; int window = 4; - int64_t t_r = in->prev_vsync, t_e = in->base_vsync, diff = 0, desync_early = 0; + double t_r = in->prev_vsync, t_e = in->base_vsync, diff = 0.0, desync_early = 0.0; for (int n = 0; n < in->drop_point; n++) { diff += t_r - t_e; t_r -= in->vsync_samples[n]; @@ -462,9 +457,9 @@ static void vsync_skip_detection(struct vo *vo) if (n == window + 1) desync_early = diff / window; } - int64_t desync = diff / in->num_vsync_samples; + double desync = diff / in->num_vsync_samples; if (in->drop_point > window * 2 && - llabs(desync - desync_early) >= in->vsync_interval * 3 / 4) + fabs(desync - desync_early) >= in->vsync_interval * 3 / 4) { // Assume a drop. An underflow can technically speaking not be a drop // (it's up to the driver what this is supposed to mean), but no reason @@ -494,7 +489,15 @@ static void update_vsync_timing_after_swap(struct vo *vo, } in->num_successive_vsyncs++; - if (in->num_successive_vsyncs <= 2) + if (in->num_successive_vsyncs <= DELAY_VSYNC_SAMPLES) + return; + + if (vsync_time <= 0 || vsync_time <= prev_vsync) { + in->prev_vsync = 0; + return; + } + + if (prev_vsync <= 0) return; if (in->num_vsync_samples >= MAX_VSYNC_SAMPLES) @@ -510,8 +513,10 @@ static void update_vsync_timing_after_swap(struct vo *vo, } double avg = 0; - for (int n = 0; n < in->num_vsync_samples; n++) + for (int n = 0; n < in->num_vsync_samples; n++) { + assert(in->vsync_samples[n] > 0); avg += in->vsync_samples[n]; + } in->estimated_vsync_interval = avg / in->num_vsync_samples; in->estimated_vsync_jitter = vsync_stddef(vo, in->vsync_interval) / in->vsync_interval; @@ -520,33 +525,33 @@ static void update_vsync_timing_after_swap(struct vo *vo, vsync_skip_detection(vo); MP_STATS(vo, "value %f jitter", in->estimated_vsync_jitter); - MP_STATS(vo, "value %f vsync-diff", in->vsync_samples[0] / 1e6); + MP_STATS(vo, "value %f vsync-diff", MP_TIME_NS_TO_S(in->vsync_samples[0])); } // to be called from VO thread only static void update_display_fps(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); if (in->internal_events & VO_EVENT_WIN_STATE) { in->internal_events &= ~(unsigned)VO_EVENT_WIN_STATE; - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); double fps = 0; vo->driver->control(vo, VOCTRL_GET_DISPLAY_FPS, &fps); - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); in->reported_display_fps = fps; } - double display_fps = vo->opts->override_display_fps; + double display_fps = vo->opts->display_fps_override; if (display_fps <= 0) display_fps = in->reported_display_fps; if (in->display_fps != display_fps) { - in->nominal_vsync_interval = display_fps > 0 ? 1e6 / display_fps : 0; + in->nominal_vsync_interval = display_fps > 0 ? 1e9 / display_fps : 0; in->vsync_interval = MPMAX(in->nominal_vsync_interval, 1); in->display_fps = display_fps; @@ -557,7 +562,7 @@ static void update_display_fps(struct vo *vo) wakeup_core(vo); } - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); } static void check_vo_caps(struct vo *vo) @@ -589,8 +594,10 @@ static void run_reconfig(void *p) mp_image_params_get_dsize(params, &vo->dwidth, &vo->dheight); + mp_mutex_lock(&vo->params_mutex); talloc_free(vo->params); vo->params = talloc_dup(vo, params); + mp_mutex_unlock(&vo->params_mutex); if (vo->driver->reconfig2) { *ret = vo->driver->reconfig2(vo, img); @@ -601,16 +608,18 @@ static void run_reconfig(void *p) if (vo->config_ok) { check_vo_caps(vo); } else { + mp_mutex_lock(&vo->params_mutex); talloc_free(vo->params); vo->params = NULL; + mp_mutex_unlock(&vo->params_mutex); } - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); talloc_free(in->current_frame); in->current_frame = NULL; forget_frames(vo); reset_vsync_timings(vo); - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); update_display_fps(vo); } @@ -699,12 +708,10 @@ void vo_wait_default(struct vo *vo, int64_t until_time) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); - if (!in->need_wakeup) { - struct timespec ts = mp_time_us_to_timespec(until_time); - pthread_cond_timedwait(&in->wakeup, &in->lock, &ts); - } - pthread_mutex_unlock(&in->lock); + mp_mutex_lock(&in->lock); + if (!in->need_wakeup) + mp_cond_timedwait_until(&in->wakeup, &in->lock, until_time); + mp_mutex_unlock(&in->lock); } // Called unlocked. @@ -717,16 +724,16 @@ static void wait_vo(struct vo *vo, int64_t until_time) } else { vo_wait_default(vo, until_time); } - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); in->need_wakeup = false; - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); } static void wakeup_locked(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_cond_broadcast(&in->wakeup); + mp_cond_broadcast(&in->wakeup); if (vo->driver->wakeup) vo->driver->wakeup(vo); in->need_wakeup = true; @@ -738,9 +745,55 @@ void vo_wakeup(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); wakeup_locked(vo); - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); +} + +static int64_t get_current_frame_end(struct vo *vo) +{ + struct vo_internal *in = vo->in; + if (!in->current_frame) + return -1; + return in->current_frame->pts + MPMAX(in->current_frame->duration, 0); +} + +static bool still_displaying(struct vo *vo) +{ + struct vo_internal *in = vo->in; + bool working = in->rendering || in->frame_queued; + if (working) + goto done; + + int64_t frame_end = get_current_frame_end(vo); + if (frame_end < 0) + goto done; + working = mp_time_ns() < frame_end; + +done: + return working && in->hasframe; +} + +// Return true if there is still a frame being displayed (or queued). +bool vo_still_displaying(struct vo *vo) +{ + mp_mutex_lock(&vo->in->lock); + bool res = still_displaying(vo); + mp_mutex_unlock(&vo->in->lock); + return res; +} + +// Make vo issue a wakeup once vo_still_displaying() becomes false. +void vo_request_wakeup_on_done(struct vo *vo) +{ + struct vo_internal *in = vo->in; + mp_mutex_lock(&vo->in->lock); + if (still_displaying(vo)) { + in->wakeup_on_done = true; + } else { + wakeup_core(vo); + } + mp_mutex_unlock(&vo->in->lock); } // Whether vo_queue_frame() can be called. If the VO is not ready yet, the @@ -754,7 +807,7 @@ void vo_wakeup(struct vo *vo) bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); bool blocked = vo->driver->initially_blocked && !(in->internal_events & VO_EVENT_INITIAL_UNBLOCK); bool r = vo->config_ok && !in->frame_queued && !blocked && @@ -766,7 +819,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) // time. next_pts -= in->timing_offset; next_pts -= in->flip_queue_offset; - int64_t now = mp_time_us(); + int64_t now = mp_time_ns(); if (next_pts > now) r = false; if (!in->wakeup_pts || next_pts < in->wakeup_pts) { @@ -776,7 +829,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) wakeup_locked(vo); } } - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); return r; } @@ -786,7 +839,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) void vo_queue_frame(struct vo *vo, struct vo_frame *frame) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); assert(vo->config_ok && !in->frame_queued && (!in->current_frame || in->current_frame->num_vsyncs < 1)); in->hasframe = true; @@ -795,7 +848,7 @@ void vo_queue_frame(struct vo *vo, struct vo_frame *frame) in->wakeup_pts = frame->display_synced ? 0 : frame->pts + MPMAX(frame->duration, 0); wakeup_locked(vo); - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); } // If a frame is currently being rendered (or queued), wait until it's done. @@ -803,10 +856,10 @@ void vo_queue_frame(struct vo *vo, struct vo_frame *frame) void vo_wait_frame(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); while (in->frame_queued || in->rendering) - pthread_cond_wait(&in->wakeup, &in->lock); - pthread_mutex_unlock(&in->lock); + mp_cond_wait(&in->wakeup, &in->lock); + mp_mutex_unlock(&in->lock); } // Wait until realtime is >= ts @@ -814,26 +867,25 @@ void vo_wait_frame(struct vo *vo) static void wait_until(struct vo *vo, int64_t target) { struct vo_internal *in = vo->in; - struct timespec ts = mp_time_us_to_timespec(target); - pthread_mutex_lock(&in->lock); - while (target > mp_time_us()) { + mp_mutex_lock(&in->lock); + while (target > mp_time_ns()) { if (in->queued_events & VO_EVENT_LIVE_RESIZING) break; - if (pthread_cond_timedwait(&in->wakeup, &in->lock, &ts)) + if (mp_cond_timedwait_until(&in->wakeup, &in->lock, target)) break; } - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); } static bool render_frame(struct vo *vo) { struct vo_internal *in = vo->in; struct vo_frame *frame = NULL; - bool got_frame = false; + bool more_frames = false; update_display_fps(vo); - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); if (in->frame_queued) { talloc_free(in->current_frame); @@ -854,7 +906,7 @@ static bool render_frame(struct vo *vo) frame->duration = -1; } - int64_t now = mp_time_us(); + int64_t now = mp_time_ns(); int64_t pts = frame->pts; int64_t duration = frame->duration; int64_t end_time = pts + duration; @@ -870,14 +922,20 @@ static bool render_frame(struct vo *vo) in->dropped_frame &= frame->can_drop; // Even if we're hopelessly behind, rather degrade to 10 FPS playback, // instead of just freezing the display forever. - in->dropped_frame &= now - in->prev_vsync < 100 * 1000; + in->dropped_frame &= now - in->prev_vsync < MP_TIME_MS_TO_NS(100); in->dropped_frame &= in->hasframe_rendered; // Setup parameters for the next time this frame is drawn. ("frame" is the // frame currently drawn, while in->current_frame is the potentially next.) in->current_frame->repeat = true; if (frame->display_synced) { - in->current_frame->vsync_offset += in->current_frame->vsync_interval; + // Increment the offset only if it's not the last vsync. The current_frame + // can still be reused. This is mostly important for redraws that might + // overshoot the target vsync point. + if (in->current_frame->num_vsyncs > 1) { + in->current_frame->vsync_offset += in->current_frame->vsync_interval; + in->current_frame->ideal_frame_vsync += in->current_frame->ideal_frame_vsync_duration; + } in->dropped_frame |= in->current_frame->num_vsyncs < 1; } if (in->current_frame->num_vsyncs > 0) @@ -891,28 +949,34 @@ static bool render_frame(struct vo *vo) in->prev_vsync = now; in->expecting_vsync = use_vsync; + // Store the initial value before we unlock. + bool request_redraw = in->request_redraw; + if (in->dropped_frame) { in->drop_count += 1; + wakeup_core(vo); } else { in->rendering = true; in->hasframe_rendered = true; int64_t prev_drop_count = vo->in->drop_count; - pthread_mutex_unlock(&in->lock); - wakeup_core(vo); // core can queue new video now + // Can the core queue new video now? Non-display-sync uses a separate + // timer instead, but possibly benefits from preparing a frame early. + bool can_queue = !in->frame_queued && + (in->current_frame->num_vsyncs < 1 || !use_vsync); + mp_mutex_unlock(&in->lock); - MP_STATS(vo, "start video-draw"); + if (can_queue) + wakeup_core(vo); - if (vo->driver->draw_frame) { - vo->driver->draw_frame(vo, frame); - } else { - vo->driver->draw_image(vo, mp_image_new_ref(frame->current)); - } + stats_time_start(in->stats, "video-draw"); - MP_STATS(vo, "end video-draw"); + vo->driver->draw_frame(vo, frame); + + stats_time_end(in->stats, "video-draw"); wait_until(vo, target); - MP_STATS(vo, "start video-flip"); + stats_time_start(in->stats, "video-flip"); vo->driver->flip_page(vo); @@ -924,12 +988,12 @@ static bool render_frame(struct vo *vo) vo->driver->get_vsync(vo, &vsync); // Make up some crap if presentation feedback is missing. - if (vsync.last_queue_display_time < 0) - vsync.last_queue_display_time = mp_time_us(); + if (vsync.last_queue_display_time <= 0) + vsync.last_queue_display_time = mp_time_ns(); - MP_STATS(vo, "end video-flip"); + stats_time_end(in->stats, "video-flip"); - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); in->dropped_frame = prev_drop_count < vo->in->drop_count; in->rendering = false; @@ -944,18 +1008,31 @@ static bool render_frame(struct vo *vo) if (in->dropped_frame) { MP_STATS(vo, "drop-vo"); } else { - in->request_redraw = false; + // If the initial redraw request was true or mpv is still playing, + // then we can clear it here since we just performed a redraw, or the + // next loop will draw what we need. However if there initially is + // no redraw request, then something can change this (i.e. the OSD) + // while the vo was unlocked. If we are paused, don't touch + // in->request_redraw in that case. + if (request_redraw || !in->paused) + in->request_redraw = false; } - pthread_cond_broadcast(&in->wakeup); // for vo_wait_frame() - wakeup_core(vo); + if (in->current_frame && in->current_frame->num_vsyncs && + in->current_frame->display_synced) + more_frames = true; + + if (in->frame_queued && in->frame_queued->display_synced) + more_frames = true; - got_frame = true; + mp_cond_broadcast(&in->wakeup); // for vo_wait_frame() done: - talloc_free(frame); - pthread_mutex_unlock(&in->lock); - return got_frame || (in->frame_queued && in->frame_queued->display_synced); + if (!vo->driver->frame_owner || in->dropped_frame) + talloc_free(frame); + mp_mutex_unlock(&in->lock); + + return more_frames; } static void do_redraw(struct vo *vo) @@ -965,7 +1042,7 @@ static void do_redraw(struct vo *vo) if (!vo->config_ok || (vo->driver->caps & VO_CAP_NORETAIN)) return; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); in->request_redraw = false; bool full_redraw = in->dropped_frame; struct vo_frame *frame = NULL; @@ -981,36 +1058,29 @@ static void do_redraw(struct vo *vo) frame->still = true; frame->pts = 0; frame->duration = -1; - pthread_mutex_unlock(&in->lock); - - if (vo->driver->draw_frame) { - vo->driver->draw_frame(vo, frame); - } else if ((full_redraw || vo->driver->control(vo, VOCTRL_REDRAW_FRAME, NULL) < 1) - && frame->current) - { - vo->driver->draw_image(vo, mp_image_new_ref(frame->current)); - } + mp_mutex_unlock(&in->lock); + vo->driver->draw_frame(vo, frame); vo->driver->flip_page(vo); - if (frame != &dummy) + if (frame != &dummy && !vo->driver->frame_owner) talloc_free(frame); } static struct mp_image *get_image_vo(void *ctx, int imgfmt, int w, int h, - int stride_align) + int stride_align, int flags) { struct vo *vo = ctx; - return vo->driver->get_image(vo, imgfmt, w, h, stride_align); + return vo->driver->get_image(vo, imgfmt, w, h, stride_align, flags); } -static void *vo_thread(void *ptr) +static MP_THREAD_VOID vo_thread(void *ptr) { struct vo *vo = ptr; struct vo_internal *in = vo->in; bool vo_paused = false; - mpthread_set_name("vo"); + mp_thread_set_name("vo"); if (vo->driver->get_image) { in->dr_helper = dr_helper_create(in->dispatch, get_image_vo, vo); @@ -1030,12 +1100,15 @@ static void *vo_thread(void *ptr) mp_dispatch_queue_process(vo->in->dispatch, 0); if (in->terminate) break; + stats_event(in->stats, "iterations"); vo->driver->control(vo, VOCTRL_CHECK_EVENTS, NULL); bool working = render_frame(vo); - int64_t now = mp_time_us(); - int64_t wait_until = now + (working ? 0 : (int64_t)1e9); + int64_t now = mp_time_ns(); + int64_t wait_until = now + MP_TIME_S_TO_NS(working ? 0 : 1000); + bool wakeup_on_done = false; + int64_t wakeup_core_after = 0; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); if (in->wakeup_pts) { if (in->wakeup_pts > now) { wait_until = MPMIN(wait_until, in->wakeup_pts); @@ -1045,16 +1118,24 @@ static void *vo_thread(void *ptr) } } if (vo->want_redraw && !in->want_redraw) { - vo->want_redraw = false; in->want_redraw = true; wakeup_core(vo); } + if ((!working && !in->rendering && !in->frame_queued) && in->wakeup_on_done) { + // At this point we know VO is going to sleep + int64_t frame_end = get_current_frame_end(vo); + if (frame_end >= 0) + wakeup_core_after = frame_end; + wakeup_on_done = true; + in->wakeup_on_done = false; + } + vo->want_redraw = false; bool redraw = in->request_redraw; bool send_reset = in->send_reset; in->send_reset = false; bool send_pause = in->paused != vo_paused; vo_paused = in->paused; - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); if (send_reset) vo->driver->control(vo, VOCTRL_RESET, NULL); @@ -1067,6 +1148,20 @@ static void *vo_thread(void *ptr) if (vo->want_redraw) // might have been set by VOCTRLs wait_until = 0; + if (wait_until <= now) + continue; + + if (wakeup_on_done) { + // At this point wait_until should be longer than frame duration + if (wakeup_core_after >= 0 && wait_until >= wakeup_core_after) { + wait_vo(vo, wakeup_core_after); + mp_mutex_lock(&in->lock); + in->need_wakeup = true; + mp_mutex_unlock(&in->lock); + } + wakeup_core(vo); + } + wait_vo(vo, wait_until); } forget_frames(vo); // implicitly synchronized @@ -1075,13 +1170,13 @@ static void *vo_thread(void *ptr) vo->driver->uninit(vo); done: TA_FREEP(&in->dr_helper); - return NULL; + MP_THREAD_RETURN(); } void vo_set_paused(struct vo *vo, bool paused) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); if (in->paused != paused) { in->paused = paused; if (in->paused && in->dropped_frame) { @@ -1091,73 +1186,55 @@ void vo_set_paused(struct vo *vo, bool paused) reset_vsync_timings(vo); wakeup_locked(vo); } - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); } int64_t vo_get_drop_count(struct vo *vo) { - pthread_mutex_lock(&vo->in->lock); + mp_mutex_lock(&vo->in->lock); int64_t r = vo->in->drop_count; - pthread_mutex_unlock(&vo->in->lock); + mp_mutex_unlock(&vo->in->lock); return r; } void vo_increment_drop_count(struct vo *vo, int64_t n) { - pthread_mutex_lock(&vo->in->lock); + mp_mutex_lock(&vo->in->lock); vo->in->drop_count += n; - pthread_mutex_unlock(&vo->in->lock); + mp_mutex_unlock(&vo->in->lock); } // Make the VO redraw the OSD at some point in the future. void vo_redraw(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); if (!in->request_redraw) { in->request_redraw = true; in->want_redraw = false; wakeup_locked(vo); } - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); } bool vo_want_redraw(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); bool r = in->want_redraw; - pthread_mutex_unlock(&in->lock); + mp_mutex_unlock(&in->lock); return r; } void vo_seek_reset(struct vo *vo) { struct vo_internal *in = vo->in; - pthread_mutex_lock(&in->lock); + mp_mutex_lock(&in->lock); forget_frames(vo); reset_vsync_timings(vo); in->send_reset = true; wakeup_locked(vo); - pthread_mutex_unlock(&in->lock); -} - -// Return true if there is still a frame being displayed (or queued). -// If this returns true, a wakeup some time in the future is guaranteed. -bool vo_still_displaying(struct vo *vo) -{ - struct vo_internal *in = vo->in; - pthread_mutex_lock(&vo->in->lock); - int64_t now = mp_time_us(); - int64_t frame_end = 0; - if (in->current_frame) { - frame_end = in->current_frame->pts + MPMAX(in->current_frame->duration, 0); - if (in->current_frame->display_synced) - frame_end = in->current_frame->num_vsyncs > 0 ? INT64_MAX : 0; - } - bool working = now < frame_end | |