From 51b17039d40d2c8ad5ed3e5cfceb8435c714db79 Mon Sep 17 00:00:00 2001 From: Themaister Date: Fri, 2 Aug 2013 22:55:48 +0200 Subject: [PATCH] Completely new approach for threaded video. Allows a good compromise between jitter and avoiding audio stutter. --- autosave.c | 2 +- gfx/thread_wrapper.c | 48 +++++++++++++++++++++++++++++++++----------- thread.c | 14 ++++++------- thread.h | 3 ++- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/autosave.c b/autosave.c index 752bec58ef..dc7e817af1 100644 --- a/autosave.c +++ b/autosave.c @@ -77,7 +77,7 @@ static void autosave_thread(void *data) slock_lock(save->cond_lock); if (!save->quit) - scond_wait_timeout(save->cond, save->cond_lock, save->interval * 1000); + scond_wait_timeout(save->cond, save->cond_lock, save->interval * 1000000LL); slock_unlock(save->cond_lock); } } diff --git a/gfx/thread_wrapper.c b/gfx/thread_wrapper.c index 1b18b09100..380fefe6e5 100644 --- a/gfx/thread_wrapper.c +++ b/gfx/thread_wrapper.c @@ -30,7 +30,6 @@ enum thread_cmd CMD_ALIVE, // Blocking alive check. Used when paused. CMD_SET_ROTATION, CMD_READ_VIEWPORT, - CMD_SET_NONBLOCK, #ifdef HAVE_OVERLAY CMD_OVERLAY_ENABLE, @@ -88,6 +87,12 @@ typedef struct thread_video bool alive; bool focus; + bool nonblock; + + rarch_time_t last_time; + rarch_time_t target_frame_time; + unsigned hit_count; + unsigned miss_count; enum thread_cmd send_cmd; enum thread_cmd reply_cmd; @@ -191,11 +196,6 @@ static void thread_loop(void *data) thread_reply(thr, CMD_FREE); return; - case CMD_SET_NONBLOCK: - thr->driver->set_nonblock_state(thr->driver_data, thr->cmd_data.b); - thread_reply(thr, CMD_SET_NONBLOCK); - break; - case CMD_SET_ROTATION: thr->driver->set_rotation(thr->driver_data, thr->cmd_data.i); thread_reply(thr, CMD_SET_ROTATION); @@ -397,6 +397,24 @@ static bool thread_frame(void *data, const void *frame_, uint8_t *dst = thr->frame.buffer; slock_lock(thr->lock); + + if (!thr->nonblock) + { + rarch_time_t target = thr->last_time + thr->target_frame_time; + // Ideally, use absolute time, but that is only a good idea on POSIX. + while (thr->frame.updated) + { + rarch_time_t current = rarch_get_time_usec(); + rarch_time_t delta = target - current; + + if (delta <= 0) + break; + + if (!scond_wait_timeout(thr->cond_cmd, thr->lock, delta)) + break; + } + } + // Drop frame if updated flag is still set, as thread is still working on last frame. if (!thr->frame.updated) { @@ -418,9 +436,6 @@ static bool thread_frame(void *data, const void *frame_, scond_signal(thr->cond_thread); - // If we are going to render menu, - // we'll want to block to avoid stepping menu - // at crazy speeds. #if defined(HAVE_RGUI) || defined(HAVE_RMENU) if (thr->texture.enable) { @@ -428,20 +443,23 @@ static bool thread_frame(void *data, const void *frame_, scond_wait(thr->cond_cmd, thr->lock); } #endif + thr->hit_count++; } + else + thr->miss_count++; + slock_unlock(thr->lock); RARCH_PERFORMANCE_STOP(thread_frame); + thr->last_time = rarch_get_time_usec(); return true; } static void thread_set_nonblock_state(void *data, bool state) { thread_video_t *thr = (thread_video_t*)data; - thr->cmd_data.b = state; - thread_send_cmd(thr, CMD_SET_NONBLOCK); - thread_wait_reply(thr, CMD_SET_NONBLOCK); + thr->nonblock = state; } static bool thread_init(thread_video_t *thr, const video_info_t *info, const input_driver_t **input, @@ -466,6 +484,9 @@ static bool thread_init(thread_video_t *thr, const video_info_t *info, const inp memset(thr->frame.buffer, 0x80, max_size); + thr->target_frame_time = (rarch_time_t)roundf(1000000LL / g_settings.video.refresh_rate); + thr->last_time = rarch_get_time_usec(); + thr->thread = sthread_create(thread_loop, thr); if (!thr->thread) return false; @@ -534,6 +555,9 @@ static void thread_free(void *data) scond_free(thr->cond_cmd); scond_free(thr->cond_thread); + RARCH_LOG("Threaded video stats: Frames pushed: %u, Frames dropped: %u.\n", + thr->hit_count, thr->miss_count); + free(thr); } diff --git a/thread.c b/thread.c index e0f1e211b8..9d92a4e79f 100644 --- a/thread.c +++ b/thread.c @@ -152,12 +152,12 @@ void scond_wait(scond_t *cond, slock_t *lock) slock_lock(lock); } -bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms) +bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us) { WaitForSingleObject(cond->event, 0); slock_unlock(lock); - DWORD res = WaitForSingleObject(cond->event, timeout_ms); + DWORD res = WaitForSingleObject(cond->event, timeout_us / 1000); slock_lock(lock); return res == WAIT_OBJECT_0; @@ -289,7 +289,7 @@ void scond_wait(scond_t *cond, slock_t *lock) } #ifndef RARCH_CONSOLE -bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms) +bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us) { struct timespec now; @@ -305,11 +305,11 @@ bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms) clock_gettime(CLOCK_REALTIME, &now); #endif - now.tv_sec += timeout_ms / 1000; - now.tv_nsec += timeout_ms * 1000000L; + now.tv_sec += timeout_us / 1000000LL; + now.tv_nsec += timeout_us * 1000LL; - now.tv_sec += now.tv_nsec / 1000000000L; - now.tv_nsec = now.tv_nsec % 1000000000L; + now.tv_sec += now.tv_nsec / 1000000000LL; + now.tv_nsec = now.tv_nsec % 1000000000LL; int ret = pthread_cond_timedwait(&cond->cond, &lock->lock, &now); return ret == 0; diff --git a/thread.h b/thread.h index 661779b343..6dbf42098e 100644 --- a/thread.h +++ b/thread.h @@ -17,6 +17,7 @@ #define THREAD_H__ #include "boolean.h" +#include // Implements the bare minimum needed for RetroArch. :) @@ -43,7 +44,7 @@ void scond_free(scond_t *cond); void scond_wait(scond_t *cond, slock_t *lock); #ifndef RARCH_CONSOLE -bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms); +bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us); #endif void scond_signal(scond_t *cond);