From 03b9bbc570d554075c3711202bff7ea18092bc21 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sun, 17 Sep 2023 19:45:21 -0500 Subject: [PATCH 1/9] vo: remove vo_get_estimated_vsync_interval conversion to seconds This is weird. The caller should be responsible for converting the value if desired. Move the conversion to player/command.c instead. --- player/command.c | 2 +- video/out/vo.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/player/command.c b/player/command.c index cf4971a4b35d2..1a4cca9befb10 100644 --- a/player/command.c +++ b/player/command.c @@ -2491,7 +2491,7 @@ static int mp_property_estimated_display_fps(void *ctx, struct m_property *prop, struct vo *vo = mpctx->video_out; if (!vo) return M_PROPERTY_UNAVAILABLE; - double interval = vo_get_estimated_vsync_interval(vo); + double interval = vo_get_estimated_vsync_interval(vo) / 1e9; if (interval <= 0) return M_PROPERTY_UNAVAILABLE; return m_property_double_ro(action, arg, 1.0 / interval); diff --git a/video/out/vo.c b/video/out/vo.c index 669595f1cedb0..496f01f45e497 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -1261,12 +1261,11 @@ double vo_get_vsync_interval(struct vo *vo) return res; } -// Returns duration of a display refresh in seconds. double vo_get_estimated_vsync_interval(struct vo *vo) { struct vo_internal *in = vo->in; pthread_mutex_lock(&in->lock); - double res = in->estimated_vsync_interval / 1e9; + double res = in->estimated_vsync_interval; pthread_mutex_unlock(&in->lock); return res; } From e9d48fddd39e818ab57181b939de8f323d5352a4 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sun, 17 Sep 2023 20:05:16 -0500 Subject: [PATCH 2/9] player: store last_time timestamp in nanoseconds --- player/core.h | 2 +- player/playloop.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/player/core.h b/player/core.h index 556620cf8ac4c..18a833595303f 100644 --- a/player/core.h +++ b/player/core.h @@ -391,7 +391,7 @@ typedef struct MPContext { double start_timestamp; // Timestamp from the last time some timing functions read the - // current time, in microseconds. + // current time, in nanoseconds. // Used to turn a new time value to a delta from last time. int64_t last_time; diff --git a/player/playloop.c b/player/playloop.c index b0df04f2d9ca8..eb42a3131c097 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -123,10 +123,10 @@ static void mp_process_input(struct MPContext *mpctx) double get_relative_time(struct MPContext *mpctx) { - int64_t new_time = mp_time_us(); + int64_t new_time = mp_time_ns(); int64_t delta = new_time - mpctx->last_time; mpctx->last_time = new_time; - return delta * 0.000001; + return delta * 1e-9; } void update_core_idle_state(struct MPContext *mpctx) From 4b7d1008716814eb2d5c0ff3134509276d931339 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sun, 17 Sep 2023 20:44:05 -0500 Subject: [PATCH 3/9] vo: use nanoseconds for frame duration and pts --- player/video.c | 4 ++-- video/out/vo.c | 20 ++++++++++---------- video/out/vo.h | 6 +++--- video/out/vo_vdpau.c | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/player/video.c b/player/video.c index 7da9cf46fe2b1..e5b0b6b3a24b1 100644 --- a/player/video.c +++ b/player/video.c @@ -1198,7 +1198,7 @@ void write_video(struct MPContext *mpctx) } double time_frame = MPMAX(mpctx->time_frame, -1); - int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6); + int64_t pts = mp_time_ns() + (int64_t)(time_frame * 1e9); // wait until VO wakes us up to get more frames // (NB: in theory, the 1st frame after display sync mode change uses the @@ -1240,7 +1240,7 @@ void write_video(struct MPContext *mpctx) diff /= mpctx->video_speed; if (mpctx->time_frame < 0) diff += mpctx->time_frame; - frame->duration = MPCLAMP(diff, 0, 10) * 1e6; + frame->duration = MPCLAMP(diff, 0, 10) * 1e9; } mpctx->video_pts = mpctx->next_frames[0]->pts; diff --git a/video/out/vo.c b/video/out/vo.c index 496f01f45e497..7b50b907457c1 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -232,7 +232,7 @@ 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); + in->timing_offset = (uint64_t)(vo->opts->timing_offset * 1e9); pthread_mutex_unlock(&in->lock); } @@ -782,7 +782,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) { @@ -830,9 +830,9 @@ 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_realtime(target); + struct timespec ts = mp_time_ns_to_realtime(target); pthread_mutex_lock(&in->lock); - while (target > mp_time_us()) { + while (target > mp_time_ns()) { if (in->queued_events & VO_EVENT_LIVE_RESIZING) break; if (pthread_cond_timedwait(&in->wakeup, &in->lock, &ts)) @@ -873,7 +873,7 @@ static bool render_frame(struct vo *vo) if (in->paused) frame->vsync_offset = 0; - 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; @@ -889,7 +889,7 @@ 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 / 1000.0) < 100 * 1000; + in->dropped_frame &= now - in->prev_vsync < 100 * 1e6; in->dropped_frame &= in->hasframe_rendered; // Setup parameters for the next time this frame is drawn. ("frame" is the @@ -907,7 +907,7 @@ static bool render_frame(struct vo *vo) bool use_vsync = in->current_frame->display_synced && !in->paused; if (use_vsync && !in->expecting_vsync) // first DS frame in a row - in->prev_vsync = now * 1000; + in->prev_vsync = now; in->expecting_vsync = use_vsync; // Store the initial value before we unlock. @@ -1230,15 +1230,15 @@ void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, out_src, out_dst, out_osd); } -// flip_page[_timed] will be called offset_us microseconds too early. +// flip_page[_timed] will be called offset_us nanoseconds too early. // (For vo_vdpau, which does its own timing.) // num_req_frames set the requested number of requested vo_frame.frames. // (For vo_gpu interpolation.) -void vo_set_queue_params(struct vo *vo, int64_t offset_us, int num_req_frames) +void vo_set_queue_params(struct vo *vo, int64_t offset_ns, int num_req_frames) { struct vo_internal *in = vo->in; pthread_mutex_lock(&in->lock); - in->flip_queue_offset = offset_us; + in->flip_queue_offset = offset_ns; in->req_frames = MPCLAMP(num_req_frames, 1, VO_MAX_REQ_FRAMES); pthread_mutex_unlock(&in->lock); } diff --git a/video/out/vo.h b/video/out/vo.h index ca120672f6fcb..d67ca5fe51d5f 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -207,10 +207,10 @@ struct vo_extra { }; struct vo_frame { - // If > 0, realtime when frame should be shown, in mp_time_us() units. + // If > 0, realtime when frame should be shown, in mp_time_ns() units. // If 0, present immediately. int64_t pts; - // Approximate frame duration, in us. + // Approximate frame duration, in ns. int duration; // Realtime of estimated distance between 2 vsync events. double vsync_interval; @@ -508,7 +508,7 @@ void vo_query_formats(struct vo *vo, uint8_t *list); void vo_event(struct vo *vo, int event); int vo_query_and_reset_events(struct vo *vo, int events); struct mp_image *vo_get_current_frame(struct vo *vo); -void vo_set_queue_params(struct vo *vo, int64_t offset_us, int num_req_frames); +void vo_set_queue_params(struct vo *vo, int64_t offset_ns, int num_req_frames); int vo_get_num_req_frames(struct vo *vo); double vo_get_vsync_interval(struct vo *vo); double vo_get_estimated_vsync_interval(struct vo *vo); diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index 17ee6aba7028c..d6b261f057a65 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -279,7 +279,7 @@ static void resize(struct vo *vo) vc->flip_offset_us = vo->opts->fullscreen ? 1000LL * vc->flip_offset_fs : 1000LL * vc->flip_offset_window; - vo_set_queue_params(vo, vc->flip_offset_us, 1); + vo_set_queue_params(vo, vc->flip_offset_us * 1000, 1); if (vc->output_surface_w < vo->dwidth || vc->output_surface_h < vo->dheight || vc->rotation != vo->params->rotate) From d927943286e378b2112221e3d06ba935027cb5b6 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sun, 17 Sep 2023 21:34:32 -0500 Subject: [PATCH 4/9] vo: change vo->driver->wait_events to nanoseconds In many cases, this is purely cosmetic because poll still only accepts microseconds. There's still a gain here however since pthread_cond_timedwait can take a realtime ts now. Additionally, 37d6604d70c8c594de2817db26356c4c950ac0fd changed the value added to timeout_ms in X11 and Wayland to ensure that it would never be 0 and rounded up. This was both incomplete, several other parts of the player have this same problem like drm, and not really needed. Instead the MPCLAMP is just adjusted to have a min of 1. --- video/out/drm_common.c | 8 ++++---- video/out/drm_common.h | 2 +- video/out/gpu/context.h | 2 +- video/out/opengl/context_drm_egl.c | 4 ++-- video/out/opengl/context_glx.c | 4 ++-- video/out/opengl/context_wayland.c | 4 ++-- video/out/opengl/context_x11egl.c | 4 ++-- video/out/vo.c | 4 ++-- video/out/vo.h | 2 +- video/out/vo_gpu.c | 6 +++--- video/out/vo_gpu_next.c | 6 +++--- video/out/vo_sdl.c | 6 +++--- video/out/vulkan/context_display.c | 2 +- video/out/vulkan/context_wayland.c | 4 ++-- video/out/vulkan/context_xlib.c | 4 ++-- video/out/wayland_common.c | 6 +++--- video/out/wayland_common.h | 2 +- video/out/x11_common.c | 6 +++--- video/out/x11_common.h | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/video/out/drm_common.c b/video/out/drm_common.c index 4f235956ad532..dd53793dbb7f8 100644 --- a/video/out/drm_common.c +++ b/video/out/drm_common.c @@ -1304,15 +1304,15 @@ void vo_drm_set_monitor_par(struct vo *vo) MP_VERBOSE(drm, "Monitor pixel aspect: %g\n", vo->monitor_par); } -void vo_drm_wait_events(struct vo *vo, int64_t until_time_us) +void vo_drm_wait_events(struct vo *vo, int64_t until_time_ns) { struct vo_drm_state *drm = vo->drm; if (drm->vt_switcher_active) { - int64_t wait_us = until_time_us - mp_time_us(); - int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000); + int64_t wait_ns = until_time_ns - mp_time_ns(); + int timeout_ms = MPCLAMP(wait_ns / 1e6, 1, 10000); vt_switcher_poll(&drm->vt_switcher, timeout_ms); } else { - vo_wait_default(vo, until_time_us); + vo_wait_default(vo, until_time_ns); } } diff --git a/video/out/drm_common.h b/video/out/drm_common.h index ac915848993aa..cadef2019f070 100644 --- a/video/out/drm_common.h +++ b/video/out/drm_common.h @@ -114,7 +114,7 @@ double vo_drm_get_display_fps(struct vo_drm_state *drm); void vo_drm_get_vsync(struct vo *vo, struct vo_vsync_info *info); void vo_drm_set_monitor_par(struct vo *vo); void vo_drm_uninit(struct vo *vo); -void vo_drm_wait_events(struct vo *vo, int64_t until_time_us); +void vo_drm_wait_events(struct vo *vo, int64_t until_time_ns); void vo_drm_wait_on_flip(struct vo_drm_state *drm); void vo_drm_wakeup(struct vo *vo); diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h index 27d8462f97cc0..6788e6fd89bbd 100644 --- a/video/out/gpu/context.h +++ b/video/out/gpu/context.h @@ -48,7 +48,7 @@ struct ra_ctx_fns { // These behave exactly like vo_driver.wakeup/wait_events. They are // optional. void (*wakeup)(struct ra_ctx *ctx); - void (*wait_events)(struct ra_ctx *ctx, int64_t until_time_us); + void (*wait_events)(struct ra_ctx *ctx, int64_t until_time_ns); void (*update_render_opts)(struct ra_ctx *ctx); // Initialize/destroy the 'struct ra' and possibly the underlying VO backend. diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c index 5141f2ce2a512..16046b1577bc4 100644 --- a/video/out/opengl/context_drm_egl.c +++ b/video/out/opengl/context_drm_egl.c @@ -734,9 +734,9 @@ static int drm_egl_control(struct ra_ctx *ctx, int *events, int request, return ret; } -static void drm_egl_wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void drm_egl_wait_events(struct ra_ctx *ctx, int64_t until_time_ns) { - vo_drm_wait_events(ctx->vo, until_time_us); + vo_drm_wait_events(ctx->vo, until_time_ns); } static void drm_egl_wakeup(struct ra_ctx *ctx) diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c index 5d22563632d6f..40622245e6cd4 100644 --- a/video/out/opengl/context_glx.c +++ b/video/out/opengl/context_glx.c @@ -334,9 +334,9 @@ static void glx_wakeup(struct ra_ctx *ctx) vo_x11_wakeup(ctx->vo); } -static void glx_wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void glx_wait_events(struct ra_ctx *ctx, int64_t until_time_ns) { - vo_x11_wait_events(ctx->vo, until_time_us); + vo_x11_wait_events(ctx->vo, until_time_ns); } const struct ra_ctx_fns ra_ctx_glx = { diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c index aa8bb2d92181f..26c52688d340f 100644 --- a/video/out/opengl/context_wayland.c +++ b/video/out/opengl/context_wayland.c @@ -198,9 +198,9 @@ static void wayland_egl_wakeup(struct ra_ctx *ctx) vo_wayland_wakeup(ctx->vo); } -static void wayland_egl_wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void wayland_egl_wait_events(struct ra_ctx *ctx, int64_t until_time_ns) { - vo_wayland_wait_events(ctx->vo, until_time_us); + vo_wayland_wait_events(ctx->vo, until_time_ns); } static void wayland_egl_update_render_opts(struct ra_ctx *ctx) diff --git a/video/out/opengl/context_x11egl.c b/video/out/opengl/context_x11egl.c index a0f710f3ecb4a..3201f298f68c0 100644 --- a/video/out/opengl/context_x11egl.c +++ b/video/out/opengl/context_x11egl.c @@ -208,9 +208,9 @@ static void mpegl_wakeup(struct ra_ctx *ctx) vo_x11_wakeup(ctx->vo); } -static void mpegl_wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void mpegl_wait_events(struct ra_ctx *ctx, int64_t until_time_ns) { - vo_x11_wait_events(ctx->vo, until_time_us); + vo_x11_wait_events(ctx->vo, until_time_ns); } const struct ra_ctx_fns ra_ctx_x11_egl = { diff --git a/video/out/vo.c b/video/out/vo.c index 7b50b907457c1..4fab586dfdfe7 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -717,7 +717,7 @@ void vo_wait_default(struct vo *vo, int64_t until_time) pthread_mutex_lock(&in->lock); if (!in->need_wakeup) { - struct timespec ts = mp_time_us_to_realtime(until_time); + struct timespec ts = mp_time_ns_to_realtime(until_time); pthread_cond_timedwait(&in->wakeup, &in->lock, &ts); } pthread_mutex_unlock(&in->lock); @@ -1064,7 +1064,7 @@ static void *vo_thread(void *ptr) 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 now = mp_time_ns(); int64_t wait_until = now + (working ? 0 : (int64_t)1e9); pthread_mutex_lock(&in->lock); diff --git a/video/out/vo.h b/video/out/vo.h index d67ca5fe51d5f..710bd6e10f109 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -414,7 +414,7 @@ struct vo_driver { * immediately. */ void (*wakeup)(struct vo *vo); - void (*wait_events)(struct vo *vo, int64_t until_time_us); + void (*wait_events)(struct vo *vo, int64_t until_time_ns); /* * Closes driver. Should restore the original state of the system. diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c index 8939561a38a8c..c02e6e730d510 100644 --- a/video/out/vo_gpu.c +++ b/video/out/vo_gpu.c @@ -252,13 +252,13 @@ static void wakeup(struct vo *vo) p->ctx->fns->wakeup(p->ctx); } -static void wait_events(struct vo *vo, int64_t until_time_us) +static void wait_events(struct vo *vo, int64_t until_time_ns) { struct gpu_priv *p = vo->priv; if (p->ctx && p->ctx->fns->wait_events) { - p->ctx->fns->wait_events(p->ctx, until_time_us); + p->ctx->fns->wait_events(p->ctx, until_time_ns); } else { - vo_wait_default(vo, until_time_us); + vo_wait_default(vo, until_time_ns); } } diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 8014f7def112d..d5c234f4bbafe 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -1578,13 +1578,13 @@ static void wakeup(struct vo *vo) p->ra_ctx->fns->wakeup(p->ra_ctx); } -static void wait_events(struct vo *vo, int64_t until_time_us) +static void wait_events(struct vo *vo, int64_t until_time_ns) { struct priv *p = vo->priv; if (p->ra_ctx && p->ra_ctx->fns->wait_events) { - p->ra_ctx->fns->wait_events(p->ra_ctx, until_time_us); + p->ra_ctx->fns->wait_events(p->ra_ctx, until_time_ns); } else { - vo_wait_default(vo, until_time_us); + vo_wait_default(vo, until_time_ns); } } diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c index b5e3d5b041e9b..e101f8d6208ba 100644 --- a/video/out/vo_sdl.c +++ b/video/out/vo_sdl.c @@ -520,10 +520,10 @@ static void wakeup(struct vo *vo) SDL_PushEvent(&event); } -static void wait_events(struct vo *vo, int64_t until_time_us) +static void wait_events(struct vo *vo, int64_t until_time_ns) { - int64_t wait_us = until_time_us - mp_time_us(); - int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000); + int64_t wait_ns = until_time_ns - mp_time_ns(); + int timeout_ms = MPCLAMP(wait_ns / 1e6, 1, 10000); SDL_Event ev; while (SDL_WaitEventTimeout(&ev, timeout_ms)) { diff --git a/video/out/vulkan/context_display.c b/video/out/vulkan/context_display.c index da801dd9217dc..84cef1e8c2046 100644 --- a/video/out/vulkan/context_display.c +++ b/video/out/vulkan/context_display.c @@ -474,7 +474,7 @@ static void display_wakeup(struct ra_ctx *ctx) // TODO } -static void display_wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void display_wait_events(struct ra_ctx *ctx, int64_t until_time_ns) { // TODO } diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c index 5ca6265a910d5..761ff5b12cf44 100644 --- a/video/out/vulkan/context_wayland.c +++ b/video/out/vulkan/context_wayland.c @@ -142,9 +142,9 @@ static void wayland_vk_wakeup(struct ra_ctx *ctx) vo_wayland_wakeup(ctx->vo); } -static void wayland_vk_wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void wayland_vk_wait_events(struct ra_ctx *ctx, int64_t until_time_ns) { - vo_wayland_wait_events(ctx->vo, until_time_us); + vo_wayland_wait_events(ctx->vo, until_time_ns); } static void wayland_vk_update_render_opts(struct ra_ctx *ctx) diff --git a/video/out/vulkan/context_xlib.c b/video/out/vulkan/context_xlib.c index 0c01d56fc9487..673dc312b7ff6 100644 --- a/video/out/vulkan/context_xlib.c +++ b/video/out/vulkan/context_xlib.c @@ -126,9 +126,9 @@ static void xlib_wakeup(struct ra_ctx *ctx) vo_x11_wakeup(ctx->vo); } -static void xlib_wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void xlib_wait_events(struct ra_ctx *ctx, int64_t until_time_ns) { - vo_x11_wait_events(ctx->vo, until_time_us); + vo_x11_wait_events(ctx->vo, until_time_ns); } const struct ra_ctx_fns ra_ctx_vulkan_xlib = { diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 47dc4d9605c4c..04b7d1f7bdb80 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -2604,12 +2604,12 @@ void vo_wayland_wait_frame(struct vo_wayland_state *wl) wl->timeout_count = 0; } -void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us) +void vo_wayland_wait_events(struct vo *vo, int64_t until_time_ns) { struct vo_wayland_state *wl = vo->wl; - int64_t wait_us = until_time_us - mp_time_us(); - int timeout_ms = MPCLAMP((wait_us + 999) / 1000, 0, 10000); + int64_t wait_ns = until_time_ns - mp_time_ns(); + int timeout_ms = MPCLAMP(wait_ns / 1e6, 1, 10000); wayland_dispatch_events(wl, 2, timeout_ms); } diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index 3b4366318cf6b..060583278ef33 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -180,7 +180,7 @@ void vo_wayland_handle_fractional_scale(struct vo_wayland_state *wl); void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, bool alpha); void vo_wayland_sync_swap(struct vo_wayland_state *wl); void vo_wayland_uninit(struct vo *vo); -void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us); +void vo_wayland_wait_events(struct vo *vo, int64_t until_time_ns); void vo_wayland_wait_frame(struct vo_wayland_state *wl); void vo_wayland_wakeup(struct vo *vo); diff --git a/video/out/x11_common.c b/video/out/x11_common.c index 78e4778ecf1fc..be5002b0683d2 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -2168,7 +2168,7 @@ void vo_x11_wakeup(struct vo *vo) (void)write(x11->wakeup_pipe[1], &(char){0}, 1); } -void vo_x11_wait_events(struct vo *vo, int64_t until_time_us) +void vo_x11_wait_events(struct vo *vo, int64_t until_time_ns) { struct vo_x11_state *x11 = vo->x11; @@ -2176,8 +2176,8 @@ void vo_x11_wait_events(struct vo *vo, int64_t until_time_us) { .fd = x11->event_fd, .events = POLLIN }, { .fd = x11->wakeup_pipe[0], .events = POLLIN }, }; - int64_t wait_us = until_time_us - mp_time_us(); - int timeout_ms = MPCLAMP((wait_us + 999) / 1000, 0, 10000); + int64_t wait_ns = until_time_ns - mp_time_ns(); + int timeout_ms = MPCLAMP(wait_ns / 1e6, 1, 10000); poll(fds, 2, timeout_ms); diff --git a/video/out/x11_common.h b/video/out/x11_common.h index 0ea0cd574f8b0..d4409f14e82d6 100644 --- a/video/out/x11_common.h +++ b/video/out/x11_common.h @@ -154,7 +154,7 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg); void vo_x11_present(struct vo *vo); void vo_x11_sync_swap(struct vo *vo); void vo_x11_wakeup(struct vo *vo); -void vo_x11_wait_events(struct vo *vo, int64_t until_time_us); +void vo_x11_wait_events(struct vo *vo, int64_t until_time_ns); void vo_x11_silence_xlib(int dir); From 7515f203a05506a349b292ebb47f67e5be1942c6 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sun, 17 Sep 2023 21:59:16 -0500 Subject: [PATCH 5/9] osdep: rename polldev to poll_wrapper Originally, this was added as purely a shim for macOS. However since we want to do high resolution polling which is not neccesarily available on all platforms, making this a generic wrapper for poll functions is useful so rename it. --- meson.build | 2 +- osdep/{polldev.c => poll_wrapper.c} | 9 +++++---- osdep/{polldev.h => poll_wrapper.h} | 0 osdep/terminal-unix.c | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) rename osdep/{polldev.c => poll_wrapper.c} (92%) rename osdep/{polldev.h => poll_wrapper.h} (100%) diff --git a/meson.build b/meson.build index 6806105caaa78..91b8e84b1542e 100644 --- a/meson.build +++ b/meson.build @@ -403,7 +403,7 @@ if posix subprocess_source = files('osdep/subprocess-posix.c') sources += path_source + subprocess_source + \ files('input/ipc-unix.c', - 'osdep/polldev.c', + 'osdep/poll_wrapper.c', 'osdep/terminal-unix.c', 'sub/filter_regex.c') endif diff --git a/osdep/polldev.c b/osdep/poll_wrapper.c similarity index 92% rename from osdep/polldev.c rename to osdep/poll_wrapper.c index dc86d5d022f58..e4ad0f698aa34 100644 --- a/osdep/polldev.c +++ b/osdep/poll_wrapper.c @@ -1,6 +1,4 @@ /* - * poll shim that supports device files on macOS. - * * This file is part of mpv. * * mpv is free software; you can redistribute it and/or @@ -22,9 +20,12 @@ #include #include -#include "osdep/polldev.h" +#include "poll_wrapper.h" + -int polldev(struct pollfd fds[], nfds_t nfds, int timeout) { +// poll shim that supports device files on macOS. +int polldev(struct pollfd fds[], nfds_t nfds, int timeout) +{ #ifdef __APPLE__ int maxfd = 0; fd_set readfds, writefds; diff --git a/osdep/polldev.h b/osdep/poll_wrapper.h similarity index 100% rename from osdep/polldev.h rename to osdep/poll_wrapper.h diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c index e03599d829b49..fabaf402e518c 100644 --- a/osdep/terminal-unix.c +++ b/osdep/terminal-unix.c @@ -31,7 +31,7 @@ #include "osdep/io.h" #include "osdep/threads.h" -#include "osdep/polldev.h" +#include "osdep/poll_wrapper.h" #include "common/common.h" #include "misc/bstr.h" From a364fbe83565c277de049a14d8afa7a8cc17ce2a Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sun, 17 Sep 2023 22:27:12 -0500 Subject: [PATCH 6/9] vo: use mp_poll wrapper in wait_events when applicable On linux, several platforms poll for events over a fd. This has ms accuracy, but mpv's timer is in ns now so lots of precision is lost. We can use an mp_poll wrapper to use ppoll instead which takes a timespec directly with nanosecond precision. On systems without ppoll this falls back to old poll behavior. On wayland, we don't actually use this because ppoll completely messes up the event loop for some unknown reason. --- meson.build | 3 +++ osdep/poll_wrapper.c | 11 +++++++++++ osdep/poll_wrapper.h | 5 +++++ video/out/drm_common.c | 9 +++++---- video/out/x11_common.c | 5 +++-- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/meson.build b/meson.build index 91b8e84b1542e..4485c74a56be1 100644 --- a/meson.build +++ b/meson.build @@ -424,6 +424,9 @@ if posix and not darwin 'osdep/timer-linux.c') endif +features += {'ppoll': cc.has_function('ppoll', args: '-D_GNU_SOURCE', + prefix: '#include ')} + cd_devices = { 'windows': 'D:', 'cygwin': 'D:', diff --git a/osdep/poll_wrapper.c b/osdep/poll_wrapper.c index e4ad0f698aa34..48a66d2cd136a 100644 --- a/osdep/poll_wrapper.c +++ b/osdep/poll_wrapper.c @@ -20,9 +20,20 @@ #include #include +#include "config.h" #include "poll_wrapper.h" +#include "timer.h" +int mp_poll(struct pollfd *fds, int nfds, int64_t timeout_ns) +{ +#if HAVE_PPOLL + struct timespec ts = mp_time_ns_to_realtime(timeout_ns); + return ppoll(fds, nfds, &ts, NULL); +#endif + return poll(fds, nfds, timeout_ns / 1e6); +} + // poll shim that supports device files on macOS. int polldev(struct pollfd fds[], nfds_t nfds, int timeout) { diff --git a/osdep/poll_wrapper.h b/osdep/poll_wrapper.h index 8593c1e77c973..b359ed39a0197 100644 --- a/osdep/poll_wrapper.h +++ b/osdep/poll_wrapper.h @@ -1,7 +1,12 @@ #pragma once #include +#include // Behaves like poll(3) but works for device files on macOS. // Only supports POLLIN and POLLOUT. int polldev(struct pollfd fds[], nfds_t nfds, int timeout); + +// Generic polling wrapper. It will try and use higher resolution +// polling (ppoll) if available. +int mp_poll(struct pollfd *fds, int nfds, int64_t timeout_ns); diff --git a/video/out/drm_common.c b/video/out/drm_common.c index dd53793dbb7f8..f8d68dfa81ad2 100644 --- a/video/out/drm_common.c +++ b/video/out/drm_common.c @@ -42,6 +42,7 @@ #include "common/msg.h" #include "options/m_config.h" #include "osdep/io.h" +#include "osdep/poll_wrapper.h" #include "osdep/timer.h" #include "misc/ctype.h" #include "video/out/vo.h" @@ -283,12 +284,12 @@ static void vt_switcher_destroy(struct vt_switcher *s) close(vt_switcher_pipe[1]); } -static void vt_switcher_poll(struct vt_switcher *s, int timeout_ms) +static void vt_switcher_poll(struct vt_switcher *s, int timeout_ns) { struct pollfd fds[1] = { { .events = POLLIN, .fd = vt_switcher_pipe[0] }, }; - poll(fds, 1, timeout_ms); + mp_poll(fds, 1, timeout_ns); if (!fds[0].revents) return; @@ -1309,8 +1310,8 @@ void vo_drm_wait_events(struct vo *vo, int64_t until_time_ns) struct vo_drm_state *drm = vo->drm; if (drm->vt_switcher_active) { int64_t wait_ns = until_time_ns - mp_time_ns(); - int timeout_ms = MPCLAMP(wait_ns / 1e6, 1, 10000); - vt_switcher_poll(&drm->vt_switcher, timeout_ms); + int64_t timeout_ns = MPCLAMP(wait_ns, 1e6, 1e10); + vt_switcher_poll(&drm->vt_switcher, timeout_ns); } else { vo_wait_default(vo, until_time_ns); } diff --git a/video/out/x11_common.c b/video/out/x11_common.c index be5002b0683d2..f77f4c86f874d 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -55,6 +55,7 @@ #include "vo.h" #include "win_state.h" #include "osdep/io.h" +#include "osdep/poll_wrapper.h" #include "osdep/timer.h" #include "osdep/subprocess.h" @@ -2177,9 +2178,9 @@ void vo_x11_wait_events(struct vo *vo, int64_t until_time_ns) { .fd = x11->wakeup_pipe[0], .events = POLLIN }, }; int64_t wait_ns = until_time_ns - mp_time_ns(); - int timeout_ms = MPCLAMP(wait_ns / 1e6, 1, 10000); + int64_t timeout_ns = MPCLAMP(wait_ns, 1e6, 1e10); - poll(fds, 2, timeout_ms); + mp_poll(fds, 2, timeout_ns); if (fds[1].revents & POLLIN) mp_flush_wakeup_pipe(x11->wakeup_pipe[0]); From 760c1a4a9b35451549da97b4a0bb57ae88e995af Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Fri, 29 Sep 2023 16:07:54 -0500 Subject: [PATCH 7/9] libmpv: add mpv_time_ns() 9606c3fca9d568dc43711017dcb35a408c0d2883 added mp_time_ns(). Since we apparently expose the mp_time_us() to clients already, there's no reason to not also expose the new nanosecond one. --- DOCS/client-api-changes.rst | 2 ++ libmpv/client.h | 11 +++++++++-- player/client.c | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst index 78ef3e789d58d..f0926471f3ff0 100644 --- a/DOCS/client-api-changes.rst +++ b/DOCS/client-api-changes.rst @@ -32,6 +32,8 @@ API changes :: + --- mpv 0.37.0 --- + 2.2 - add mpv_time_ns() --- mpv 0.36.0 --- 2.1 - add mpv_del_property() --- mpv 0.35.0 --- diff --git a/libmpv/client.h b/libmpv/client.h index 4199744ba7f4d..0a548a58eb633 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -248,7 +248,7 @@ extern "C" { * relational operators (<, >, <=, >=). */ #define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL) -#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(2, 1) +#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(2, 2) /** * The API user is allowed to "#define MPV_ENABLE_DEPRECATED 0" before @@ -602,7 +602,7 @@ MPV_EXPORT mpv_handle *mpv_create_weak_client(mpv_handle *ctx, const char *name) MPV_EXPORT int mpv_load_config_file(mpv_handle *ctx, const char *filename); /** - * Return the internal time in microseconds. This has an arbitrary start offset, + * Return the internal time in nanoseconds. This has an arbitrary start offset, * but will never wrap or go backwards. * * Note that this is always the real time, and doesn't necessarily have to do @@ -615,6 +615,11 @@ MPV_EXPORT int mpv_load_config_file(mpv_handle *ctx, const char *filename); * * Safe to be called from mpv render API threads. */ +MPV_EXPORT int64_t mpv_get_time_ns(mpv_handle *ctx); + +/** + * Same as mpv_get_time_ns but in microseconds. + */ MPV_EXPORT int64_t mpv_get_time_us(mpv_handle *ctx); /** @@ -1951,6 +1956,8 @@ MPV_DEFINE_SYM_PTR(mpv_create_weak_client) #define mpv_create_weak_client pfn_mpv_create_weak_client MPV_DEFINE_SYM_PTR(mpv_load_config_file) #define mpv_load_config_file pfn_mpv_load_config_file +MPV_DEFINE_SYM_PTR(mpv_get_time_ns) +#define mpv_get_time_ns pfn_mpv_get_time_ns MPV_DEFINE_SYM_PTR(mpv_get_time_us) #define mpv_get_time_us pfn_mpv_get_time_us MPV_DEFINE_SYM_PTR(mpv_free_node_contents) diff --git a/player/client.c b/player/client.c index 71e4fb99c7959..f16af4528e0c4 100644 --- a/player/client.c +++ b/player/client.c @@ -2129,6 +2129,11 @@ void mpv_free(void *data) talloc_free(data); } +int64_t mpv_get_time_ns(mpv_handle *ctx) +{ + return mp_time_ns(); +} + int64_t mpv_get_time_us(mpv_handle *ctx) { return mp_time_us(); From be27145100a7a0c6a9e5c89eb502ed69cd1ec413 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Fri, 29 Sep 2023 17:24:21 -0500 Subject: [PATCH 8/9] timer: change mp_sleep_us to mp_sleep_ns Linux and macOS already use nanosecond resolution for their sleep functions. It was just being converted from microseconds before. Since we have mp_time_ns now, go ahead and bump the precision here. The timer for windows uses some timeBeginPeriod thing which I'm not sure what it does really but whatever just convert the units to ms like they were doing before. There's really no reason to keep the mp_sleep_us helper around. A multiplication by 1000 is trivial and underlying OS clocks have nanosecond precision. --- audio/out/ao_wasapi_utils.c | 6 +++--- osdep/timer-darwin.c | 5 ++--- osdep/timer-linux.c | 8 ++++---- osdep/timer-win2.c | 12 ++++++------ osdep/timer.h | 6 +++--- video/out/opengl/hwdec_dxva2egl.c | 6 +++--- video/out/vo_null.c | 8 ++++---- video/out/vo_x11.c | 2 +- video/out/vo_xv.c | 2 +- 9 files changed, 27 insertions(+), 28 deletions(-) diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 07c7ddb43f6f0..6c5bfb3ab1f15 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -945,7 +945,7 @@ bool wasapi_thread_init(struct ao *ao) { struct wasapi_state *state = ao->priv; MP_DBG(ao, "Init wasapi thread\n"); - int64_t retry_wait = 1; + int64_t retry_wait = 1000; bool align_hack = false; HRESULT hr; @@ -1028,13 +1028,13 @@ bool wasapi_thread_init(struct ao *ao) goto retry; case AUDCLNT_E_DEVICE_IN_USE: case AUDCLNT_E_DEVICE_INVALIDATED: - if (retry_wait > 8) { + if (retry_wait > 8000) { MP_FATAL(ao, "Bad device retry failed\n"); return false; } wasapi_thread_uninit(ao); MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait); - mp_sleep_us(retry_wait); + mp_sleep_ns(retry_wait); retry_wait *= 2; goto retry; } diff --git a/osdep/timer-darwin.c b/osdep/timer-darwin.c index a114d0d727067..bb8a9b43248b8 100644 --- a/osdep/timer-darwin.c +++ b/osdep/timer-darwin.c @@ -28,10 +28,9 @@ static double timebase_ratio_ns; -void mp_sleep_us(int64_t us) +void mp_sleep_ns(int64_t ns) { - uint64_t deadline = us * 1e3 / timebase_ratio_ns + mach_absolute_time(); - + uint64_t deadline = ns / timebase_ratio_ns + mach_absolute_time(); mach_wait_until(deadline); } diff --git a/osdep/timer-linux.c b/osdep/timer-linux.c index 0289233695206..ff4b137cb227e 100644 --- a/osdep/timer-linux.c +++ b/osdep/timer-linux.c @@ -22,13 +22,13 @@ #include #include "timer.h" -void mp_sleep_us(int64_t us) +void mp_sleep_ns(int64_t ns) { - if (us < 0) + if (ns < 0) return; struct timespec ts; - ts.tv_sec = us / 1000000; - ts.tv_nsec = (us % 1000000) * 1000; + ts.tv_sec = ns / UINT64_C(1000000000); + ts.tv_nsec = ns % UINT64_C(1000000000); nanosleep(&ts, NULL); } diff --git a/osdep/timer-win2.c b/osdep/timer-win2.c index dd7a42f0d1600..db4c39d7eff3a 100644 --- a/osdep/timer-win2.c +++ b/osdep/timer-win2.c @@ -52,17 +52,17 @@ void mp_end_hires_timers(int res_ms) #endif } -void mp_sleep_us(int64_t us) +void mp_sleep_ns(int64_t ns) { - if (us < 0) + if (ns < 0) return; // Sleep(0) won't sleep for one clocktick as the unix usleep // instead it will only make the thread ready // it may take some time until it actually starts to run again - if (us < 1000) - us = 1000; - int hrt = mp_start_hires_timers(us / 1000); - Sleep(us / 1000); + if (ns < 1e6) + ns = 1e6; + int hrt = mp_start_hires_timers(ns / 1e6); + Sleep(ns / 1e6); mp_end_hires_timers(hrt); } diff --git a/osdep/timer.h b/osdep/timer.h index d76ad16129bec..b273cb405e504 100644 --- a/osdep/timer.h +++ b/osdep/timer.h @@ -29,7 +29,7 @@ int64_t mp_time_us(void); // Return time in nanoseconds. Never wraps. Never returns 0 or negative values. int64_t mp_time_ns(void); -// Return time in seconds. Can have down to 1 microsecond resolution, but will +// Return time in seconds. Can have down to 1 nanosecond resolution, but will // be much worse when casted to float. double mp_time_sec(void); @@ -37,8 +37,8 @@ double mp_time_sec(void); void mp_raw_time_init(void); uint64_t mp_raw_time_ns(void); -// Sleep in microseconds. -void mp_sleep_us(int64_t us); +// Sleep in nanoseconds. +void mp_sleep_ns(int64_t ns); #ifdef _WIN32 // returns: timer resolution in ms if needed and started successfully, else 0 diff --git a/video/out/opengl/hwdec_dxva2egl.c b/video/out/opengl/hwdec_dxva2egl.c index d870d982cd14c..0ca10ef7af804 100644 --- a/video/out/opengl/hwdec_dxva2egl.c +++ b/video/out/opengl/hwdec_dxva2egl.c @@ -343,7 +343,7 @@ static int mapper_map(struct ra_hwdec_mapper *mapper) // of the above StretchRect. Timeout of 8ms is required to reliably // render 4k on Intel Haswell, Ivybridge and Cherry Trail Atom. const int max_retries = 8; - const int64_t wait_us = 1000; + const int64_t wait_ns = 1e6; int retries = 0; while (true) { hr = IDirect3DQuery9_GetData(p->query9, NULL, 0, D3DGETDATA_FLUSH); @@ -353,10 +353,10 @@ static int mapper_map(struct ra_hwdec_mapper *mapper) } else if (hr == S_FALSE) { if (++retries > max_retries) { MP_VERBOSE(mapper, "Failed to flush frame after %lld ms\n", - (long long)(wait_us * max_retries) / 1000); + (long long)(wait_ns * max_retries) / 1e6); break; } - mp_sleep_us(wait_us); + mp_sleep_ns(wait_ns); } else { break; } diff --git a/video/out/vo_null.c b/video/out/vo_null.c index 2aa46f785f752..0c49062e23d38 100644 --- a/video/out/vo_null.c +++ b/video/out/vo_null.c @@ -40,14 +40,14 @@ static void flip_page(struct vo *vo) { struct priv *p = vo->priv; if (p->cfg_fps) { - int64_t ft = 1e6 / p->cfg_fps; - int64_t prev_vsync = mp_time_us() / ft; + int64_t ft = 1e9 / p->cfg_fps; + int64_t prev_vsync = mp_time_ns() / ft; int64_t target_time = (prev_vsync + 1) * ft; for (;;) { - int64_t now = mp_time_us(); + int64_t now = mp_time_ns(); if (now >= target_time) break; - mp_sleep_us(target_time - now); + mp_sleep_ns(target_time - now); } } } diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c index 9775b2394899c..1087fc249efd8 100644 --- a/video/out/vo_x11.c +++ b/video/out/vo_x11.c @@ -299,7 +299,7 @@ static void wait_for_completion(struct vo *vo, int max_outstanding) " for XShm completion events...\n"); ctx->Shm_Warned_Slow = 1; } - mp_sleep_us(1000); + mp_sleep_ns(1e6); vo_x11_check_events(vo); } } diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c index 22bce037709b2..cffd3a797fb02 100644 --- a/video/out/vo_xv.c +++ b/video/out/vo_xv.c @@ -670,7 +670,7 @@ static void wait_for_completion(struct vo *vo, int max_outstanding) " for XShm completion events...\n"); ctx->Shm_Warned_Slow = 1; } - mp_sleep_us(1000); + mp_sleep_ns(1e6); vo_x11_check_events(vo); } } From a016025c62af4d7086dfe5d61ec4a7e3b9719f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sat, 29 Jul 2023 14:07:03 +0200 Subject: [PATCH 9/9] osdep/timer-win2: use Waitable Timer for sleeping Allows higher resolution sleeps than Sleep which has milliseconds resolution. In practice Windows kernel does not really go below 0.5ms, but we don't have to limit ourselves on API side of things and do the best we can. --- osdep/timer-win2.c | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/osdep/timer-win2.c b/osdep/timer-win2.c index db4c39d7eff3a..7867b5a5251dd 100644 --- a/osdep/timer-win2.c +++ b/osdep/timer-win2.c @@ -56,13 +56,37 @@ void mp_sleep_ns(int64_t ns) { if (ns < 0) return; - // Sleep(0) won't sleep for one clocktick as the unix usleep - // instead it will only make the thread ready - // it may take some time until it actually starts to run again - if (ns < 1e6) - ns = 1e6; - int hrt = mp_start_hires_timers(ns / 1e6); - Sleep(ns / 1e6); + + int hrt = mp_start_hires_timers(ns < 1e6 ? 1 : ns / 1e6); + +#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION +#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x2 +#endif + + HANDLE timer = CreateWaitableTimerEx(NULL, NULL, + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + TIMER_ALL_ACCESS); + + // CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is supported in Windows 10 1803+, + // retry without it. + if (!timer) + timer = CreateWaitableTimerEx(NULL, NULL, 0, TIMER_ALL_ACCESS); + + if (!timer) + goto end; + + // Time is expected in 100 nanosecond intervals. + // Negative values indicate relative time. + LARGE_INTEGER time = (LARGE_INTEGER){ .QuadPart = -(ns / 100) }; + if (!SetWaitableTimer(timer, &time, 0, NULL, NULL, 0)) + goto end; + + if (WaitForSingleObject(timer, INFINITE) != WAIT_OBJECT_0) + goto end; + +end: + if (timer) + CloseHandle(timer); mp_end_hires_timers(hrt); }