diff --git a/Makefile b/Makefile
index 7baff23a57..58212f2bec 100644
--- a/Makefile
+++ b/Makefile
@@ -77,9 +77,8 @@ ifneq ($(findstring Linux,$(OS)),)
JOYCONFIG_OBJ += input/linuxraw_joypad.o
endif
-OBJ += autosave.o thread.o
-
ifeq ($(HAVE_THREADS), 1)
+ OBJ += autosave.o thread.o gfx/thread_wrapper.o
ifeq ($(findstring Haiku,$(OS)),)
LIBS += -lpthread
endif
diff --git a/Makefile.win b/Makefile.win
index 79396f99bc..a038dece6a 100644
--- a/Makefile.win
+++ b/Makefile.win
@@ -99,7 +99,7 @@ ifeq ($(HAVE_SDL), 1)
endif
ifeq ($(HAVE_THREADS), 1)
- OBJ += autosave.o thread.o
+ OBJ += autosave.o thread.o gfx/thread_wrapper.o
DEFINES += -DHAVE_THREADS
endif
diff --git a/config.def.h b/config.def.h
index 7a0cf21cac..779117199b 100644
--- a/config.def.h
+++ b/config.def.h
@@ -218,6 +218,9 @@ static const bool disable_composition = false;
// Video VSYNC (recommended)
static const bool vsync = true;
+// Threaded video. Will possibly increase performance significantly at cost of worse synchronization and latency.
+static const bool video_threaded = false;
+
// Smooths picture
static const bool video_smooth = true;
diff --git a/driver.c b/driver.c
index 364ecba733..5db2e09af8 100644
--- a/driver.c
+++ b/driver.c
@@ -23,6 +23,7 @@
#include "compat/posix_string.h"
#include "audio/utils.h"
#include "audio/resampler.h"
+#include "gfx/thread_wrapper.h"
#ifdef HAVE_X11
#include "gfx/context/x11_common.h"
@@ -492,6 +493,12 @@ static void compute_audio_buffer_statistics(void)
static void compute_monitor_fps_statistics(void)
{
+ if (g_settings.video.threaded)
+ {
+ RARCH_LOG("Monitor FPS estimation is disabled for threaded video.\n");
+ return;
+ }
+
if (g_extern.measure_data.frame_time_samples_count < 2 * MEASURE_FRAME_TIME_SAMPLES_COUNT)
{
RARCH_LOG("Does not have enough samples for monitor refresh rate estimation. Requires to run for at least %u frames.\n",
@@ -800,7 +807,22 @@ void init_video_input(void)
video.rgb32 = g_extern.filter.active || (g_extern.system.pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888);
const input_driver_t *tmp = driver.input;
- driver.video_data = video_init_func(&video, &driver.input, &driver.input_data);
+#ifdef HAVE_THREADS
+ if (g_settings.video.threaded)
+ {
+ find_video_driver(); // Need to grab the "real" video driver interface on a reinit.
+ RARCH_LOG("Starting threaded video driver ...\n");
+ if (!rarch_threaded_video_init(&driver.video, &driver.video_data,
+ &driver.input, &driver.input_data,
+ driver.video, &video))
+ {
+ RARCH_ERR("Cannot open threaded video driver ... Exiting ...\n");
+ rarch_fail(1, "init_video_input()");
+ }
+ }
+ else
+#endif
+ driver.video_data = video_init_func(&video, &driver.input, &driver.input_data);
if (driver.video_data == NULL)
{
diff --git a/driver.h b/driver.h
index 580999e379..59c06b63ee 100644
--- a/driver.h
+++ b/driver.h
@@ -280,6 +280,8 @@ typedef struct driver
void *video_data;
void *input_data;
+ bool threaded_video;
+
// Set if the respective handles are owned by RetroArch driver core.
// Consoles upper logic will generally intialize the drivers before
// the driver core initializes. It will then be up to upper logic
diff --git a/general.h b/general.h
index e6bfa575fe..4398447637 100644
--- a/general.h
+++ b/general.h
@@ -173,6 +173,7 @@ struct settings
char filter_path[PATH_MAX];
enum rarch_shader_type shader_type;
float refresh_rate;
+ bool threaded;
bool render_to_texture;
diff --git a/gfx/d3d9/d3d9.cpp b/gfx/d3d9/d3d9.cpp
index 70efc7a69e..856838f6d3 100644
--- a/gfx/d3d9/d3d9.cpp
+++ b/gfx/d3d9/d3d9.cpp
@@ -1160,9 +1160,12 @@ static void *d3d9_init(const video_info_t *info, const input_driver_t **input,
if (!vid)
return nullptr;
- void *dinput = input_dinput.init();
- *input = dinput ? &input_dinput : nullptr;
- *input_data = dinput;
+ if (input && input_data)
+ {
+ void *dinput = input_dinput.init();
+ *input = dinput ? &input_dinput : nullptr;
+ *input_data = dinput;
+ }
return vid;
}
diff --git a/gfx/gl.c b/gfx/gl.c
index 0ebd219329..ac3a6739b8 100644
--- a/gfx/gl.c
+++ b/gfx/gl.c
@@ -1691,7 +1691,8 @@ static void *gl_init(const video_info_t *video, const input_driver_t **input, vo
gl_init_textures(gl, video);
gl_init_textures_data(gl);
- context_input_driver_func(input, input_data);
+ if (input && input_data)
+ context_input_driver_func(input, input_data);
#ifndef HAVE_RMENU
// Comes too early for console - moved to gl_start
diff --git a/gfx/sdl_gfx.c b/gfx/sdl_gfx.c
index 0f30ad2778..2b2319af86 100644
--- a/gfx/sdl_gfx.c
+++ b/gfx/sdl_gfx.c
@@ -231,16 +231,19 @@ static void *sdl_gfx_init(const video_info_t *video, const input_driver_t **inpu
sdl_gfx_set_handles();
- sdl_input = input_sdl.init();
- if (sdl_input)
+ if (input && input_data)
{
- *input = &input_sdl;
- *input_data = sdl_input;
- }
- else
- {
- *input = NULL;
- *input_data = NULL;
+ sdl_input = input_sdl.init();
+ if (sdl_input)
+ {
+ *input = &input_sdl;
+ *input_data = sdl_input;
+ }
+ else
+ {
+ *input = NULL;
+ *input_data = NULL;
+ }
}
sdl_init_font(vid, g_settings.video.font_path, g_settings.video.font_size);
diff --git a/gfx/thread_wrapper.c b/gfx/thread_wrapper.c
new file mode 100644
index 0000000000..6f5337b478
--- /dev/null
+++ b/gfx/thread_wrapper.c
@@ -0,0 +1,316 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Hans-Kristian Arntzen
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include "thread_wrapper.h"
+#include "../thread.h"
+#include "../general.h"
+#include
+#include
+#include
+
+enum thread_cmd
+{
+ CMD_NONE = 0,
+ CMD_INIT,
+ CMD_SET_SHADER,
+ CMD_FREE,
+ CMD_SET_ROTATION,
+ CMD_VIEWPORT_INFO,
+ CMD_READ_VIEWPORT,
+ CMD_SET_NONBLOCK,
+
+ CMD_DUMMY = INT_MAX
+};
+
+typedef struct thread_video
+{
+ slock_t *lock;
+ scond_t *cond_cmd;
+ scond_t *cond_thread;
+ sthread_t *thread;
+
+ video_info_t info;
+ const video_driver_t *driver;
+ void *driver_data;
+ const input_driver_t **input;
+ void **input_data;
+
+ bool alive;
+ bool focus;
+
+ enum thread_cmd send_cmd;
+ enum thread_cmd reply_cmd;
+ union
+ {
+ bool b;
+ int i;
+ const char *str;
+ void *v;
+ } cmd_data;
+
+ struct
+ {
+ slock_t *lock;
+ uint8_t *buffer;
+ unsigned width;
+ unsigned height;
+ unsigned pitch;
+ bool updated;
+ char msg[1024];
+ } frame;
+
+} thread_video_t;
+
+static void *thread_init_never_call(const video_info_t *video, const input_driver_t **input, void **input_data)
+{
+ (void)video;
+ (void)input;
+ (void)input_data;
+ RARCH_ERR("Sanity check fail! Threaded mustn't be reinit.\n");
+ abort();
+ return NULL;
+}
+
+static void thread_reply(thread_video_t *thr, enum thread_cmd cmd)
+{
+ slock_lock(thr->lock);
+ thr->reply_cmd = cmd;
+ thr->send_cmd = CMD_NONE;
+ scond_signal(thr->cond_cmd);
+ slock_unlock(thr->lock);
+}
+
+static void thread_loop(void *data)
+{
+ thread_video_t *thr = (thread_video_t*)data;
+
+ for (;;)
+ {
+ bool updated = false;
+ slock_lock(thr->lock);
+ while (thr->send_cmd == CMD_NONE && !thr->frame.updated)
+ scond_wait(thr->cond_thread, thr->lock);
+ if (thr->frame.updated)
+ updated = true;
+ slock_unlock(thr->lock);
+
+ switch (thr->send_cmd)
+ {
+ case CMD_INIT:
+ //fprintf(stderr, "CMD_INIT\n");
+ thr->driver_data = thr->driver->init(&thr->info, thr->input, thr->input_data);
+ thr->cmd_data.b = thr->driver_data;
+ thread_reply(thr, CMD_INIT);
+ break;
+
+ case CMD_FREE:
+ //fprintf(stderr, "CMD_FREE\n");
+ if (thr->driver_data)
+ thr->driver->free(thr->driver_data);
+ thr->driver_data = NULL;
+ thread_reply(thr, CMD_FREE);
+ return;
+
+ case CMD_SET_NONBLOCK:
+ //fprintf(stderr, "CMD_SET_NONBLOCK\n");
+ thr->driver->set_nonblock_state(thr->driver_data, thr->cmd_data.b);
+ thread_reply(thr, CMD_SET_NONBLOCK);
+ break;
+
+ default:
+ //fprintf(stderr, "CMD unknown ...\n");
+ thread_reply(thr, thr->send_cmd);
+ break;
+ }
+
+ if (updated)
+ {
+ //fprintf(stderr, "RUN FRAME\n");
+ slock_lock(thr->frame.lock);
+ bool ret = thr->driver->frame(thr->driver_data,
+ thr->frame.buffer, thr->frame.width, thr->frame.height,
+ thr->frame.pitch, *thr->frame.msg ? thr->frame.msg : NULL);
+ slock_unlock(thr->frame.lock);
+
+ bool alive = ret && thr->driver->alive(thr->driver_data);
+ bool focus = ret && thr->driver->focus(thr->driver_data);
+ //fprintf(stderr, "Alive: %d, Focus: %d.\n", alive, focus);
+
+ slock_lock(thr->lock);
+ thr->alive = alive;
+ thr->focus = focus;
+ thr->frame.updated = false;
+ slock_unlock(thr->lock);
+ }
+ }
+}
+
+static void thread_send_cmd(thread_video_t *thr, enum thread_cmd cmd)
+{
+ slock_lock(thr->lock);
+ thr->send_cmd = cmd;
+ thr->reply_cmd = CMD_NONE;
+ scond_signal(thr->cond_thread);
+ slock_unlock(thr->lock);
+}
+
+static void thread_wait_reply(thread_video_t *thr, enum thread_cmd cmd)
+{
+ slock_lock(thr->lock);
+ while (cmd != thr->reply_cmd)
+ scond_wait(thr->cond_cmd, thr->lock);
+ slock_unlock(thr->lock);
+}
+
+static bool thread_alive(void *data)
+{
+ thread_video_t *thr = (thread_video_t*)data;
+ slock_lock(thr->lock);
+ bool ret = thr->alive;
+ slock_unlock(thr->lock);
+ return ret;
+}
+
+static bool thread_focus(void *data)
+{
+ thread_video_t *thr = (thread_video_t*)data;
+ slock_lock(thr->lock);
+ bool ret = thr->focus;
+ slock_unlock(thr->lock);
+ return ret;
+}
+
+static bool thread_frame(void *data, const void *frame_,
+ unsigned width, unsigned height, unsigned pitch, const char *msg)
+{
+ if (!frame_)
+ return true;
+
+ thread_video_t *thr = (thread_video_t*)data;
+ unsigned copy_stride = width * (thr->info.rgb32 ? sizeof(uint32_t) : sizeof(uint16_t));
+
+ const uint8_t *src = (const uint8_t*)frame_;
+ uint8_t *dst = thr->frame.buffer;
+
+ slock_lock(thr->lock);
+ // Drop frame if updated flag is still set, as thread is still working on last frame.
+ if (!thr->frame.updated)
+ {
+ slock_lock(thr->frame.lock);
+ for (unsigned h = 0; h < height; h++, src += pitch, dst += copy_stride)
+ memcpy(dst, src, copy_stride);
+ thr->frame.updated = true;
+ scond_signal(thr->cond_thread);
+ thr->frame.width = width;
+ thr->frame.height = height;
+ thr->frame.pitch = copy_stride;
+
+ if (msg)
+ strlcpy(thr->frame.msg, msg, sizeof(thr->frame.msg));
+ else
+ *thr->frame.msg = '\0';
+
+ slock_unlock(thr->frame.lock);
+ }
+ slock_unlock(thr->lock);
+
+ 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);
+}
+
+static bool thread_init(thread_video_t *thr, const video_info_t *info, const input_driver_t **input,
+ void **input_data)
+{
+ thr->lock = slock_new();
+ thr->frame.lock = slock_new();
+ thr->cond_cmd = scond_new();
+ thr->cond_thread = scond_new();
+ thr->input = input;
+ thr->input_data = input_data;
+ thr->info = *info;
+ thr->alive = true;
+ thr->focus = true;
+
+ thr->frame.buffer = (uint8_t*)malloc((info->rgb32 ? sizeof(uint32_t) : sizeof(uint16_t)) *
+ info->input_scale * info->input_scale * RARCH_SCALE_BASE * RARCH_SCALE_BASE);
+ if (!thr->frame.buffer)
+ return false;
+
+ thr->thread = sthread_create(thread_loop, thr);
+ if (!thr->thread)
+ return false;
+ thread_send_cmd(thr, CMD_INIT);
+ thread_wait_reply(thr, CMD_INIT);
+ return thr->cmd_data.b;
+}
+
+static void thread_free(void *data)
+{
+ thread_video_t *thr = (thread_video_t*)data;
+ if (!thr)
+ return;
+
+ thread_send_cmd(thr, CMD_FREE);
+ thread_wait_reply(thr, CMD_FREE);
+ sthread_join(thr->thread);
+
+ free(thr->frame.buffer);
+ slock_free(thr->frame.lock);
+ slock_free(thr->lock);
+ scond_free(thr->cond_cmd);
+ scond_free(thr->cond_thread);
+
+ free(thr);
+}
+
+static const video_driver_t video_thread = {
+ thread_init_never_call, // Should never be called directly.
+ thread_frame,
+ thread_set_nonblock_state,
+ thread_alive,
+ thread_focus,
+ NULL, // set_shader
+ thread_free,
+ "Thread wrapper",
+ NULL, // set_rotation
+ NULL, // viewport_info
+ NULL, // read_viewport
+#ifdef HAVE_OVERLAY
+ NULL, // get_overlay_interface
+#endif
+};
+
+bool rarch_threaded_video_init(const video_driver_t **out_driver, void **out_data,
+ const input_driver_t **input, void **input_data,
+ const video_driver_t *driver, const video_info_t *info)
+{
+ thread_video_t *thr = (thread_video_t*)calloc(1, sizeof(*thr));
+ if (!thr)
+ return false;
+
+ thr->driver = driver;
+ *out_driver = &video_thread;
+ *out_data = thr;
+ return thread_init(thr, info, input, input_data);
+}
+
+
diff --git a/gfx/thread_wrapper.h b/gfx/thread_wrapper.h
new file mode 100644
index 0000000000..6019803525
--- /dev/null
+++ b/gfx/thread_wrapper.h
@@ -0,0 +1,24 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Hans-Kristian Arntzen
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include "../driver.h"
+#include "../boolean.h"
+
+// Starts a video driver in a new thread.
+// Access to video driver will be mediated through this driver.
+bool rarch_threaded_video_init(const video_driver_t **out_driver, void **out_data,
+ const input_driver_t **input, void **input_data,
+ const video_driver_t *driver, const video_info_t *info);
+
diff --git a/gfx/xvideo.c b/gfx/xvideo.c
index a4db814332..ded1a37cd7 100644
--- a/gfx/xvideo.c
+++ b/gfx/xvideo.c
@@ -464,14 +464,17 @@ static void *xv_init(const video_info_t *video, const input_driver_t **input, vo
driver.video_display = (uintptr_t)xv->display;
driver.video_window = (Window)xv->window;
- xinput = input_x.init();
- if (xinput)
+ if (input && input_data)
{
- *input = &input_x;
- *input_data = xinput;
+ xinput = input_x.init();
+ if (xinput)
+ {
+ *input = &input_x;
+ *input_data = xinput;
+ }
+ else
+ *input = NULL;
}
- else
- *input = NULL;
init_yuv_tables(xv);
xv_init_font(xv, g_settings.video.font_path, g_settings.video.font_size);
diff --git a/retroarch.cfg b/retroarch.cfg
index 2237d20e35..22248ce2cf 100644
--- a/retroarch.cfg
+++ b/retroarch.cfg
@@ -65,6 +65,9 @@
# Video vsync.
# video_vsync = true
+# Use threaded video driver. Using this might improve performance at possible cost of latency and more video stuttering.
+# video_threaded = false
+
# Smoothens picture with bilinear filtering. Should be disabled if using pixel shaders.
# video_smooth = true
diff --git a/settings.c b/settings.c
index d6a798f20f..217924c87d 100644
--- a/settings.c
+++ b/settings.c
@@ -159,6 +159,7 @@ void config_set_defaults(void)
g_settings.video.fullscreen_y = fullscreen_y;
g_settings.video.disable_composition = disable_composition;
g_settings.video.vsync = vsync;
+ g_settings.video.threaded = video_threaded;
g_settings.video.smooth = video_smooth;
g_settings.video.force_aspect = force_aspect;
g_settings.video.scale_integer = scale_integer;
@@ -438,6 +439,7 @@ bool config_load_file(const char *path)
CONFIG_GET_INT(video.monitor_index, "video_monitor_index");
CONFIG_GET_BOOL(video.disable_composition, "video_disable_composition");
CONFIG_GET_BOOL(video.vsync, "video_vsync");
+ CONFIG_GET_BOOL(video.threaded, "video_threaded");
CONFIG_GET_BOOL(video.smooth, "video_smooth");
CONFIG_GET_BOOL(video.force_aspect, "video_force_aspect");
CONFIG_GET_BOOL(video.scale_integer, "video_scale_integer");