RetroArch/gfx/thread_wrapper.c
Themaister 1cececac18 Add get_proc_address.
Render something "real" in GL.
2013-03-28 01:11:32 +01:00

765 lines
21 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
#include "thread_wrapper.h"
#include "../thread.h"
#include "../general.h"
#include "../performance.h"
#include <stdlib.h>
#include <string.h>
#include <limits.h>
enum thread_cmd
{
CMD_NONE = 0,
CMD_INIT,
CMD_SET_SHADER,
CMD_FREE,
CMD_ALIVE, // Blocking alive check. Used when paused.
CMD_SET_ROTATION,
CMD_READ_VIEWPORT,
CMD_SET_NONBLOCK,
#ifdef HAVE_OVERLAY
CMD_OVERLAY_ENABLE,
CMD_OVERLAY_LOAD,
CMD_OVERLAY_TEX_GEOM,
CMD_OVERLAY_VERTEX_GEOM,
CMD_OVERLAY_FULL_SCREEN,
CMD_OVERLAY_SET_ALPHA,
#endif
CMD_POKE_SET_BLEND,
CMD_POKE_SET_FILTERING,
#ifdef HAVE_FBO
CMD_POKE_SET_FBO_STATE,
CMD_POKE_GET_FBO_STATE,
#endif
CMD_POKE_SET_ASPECT_RATIO,
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;
#ifdef HAVE_OVERLAY
const video_overlay_interface_t *overlay;
#endif
const video_poke_interface_t *poke;
void *driver_data;
const input_driver_t **input;
void **input_data;
#ifdef HAVE_RGUI
const void *rgui_texture;
#endif
bool apply_state_changes;
bool alive;
bool focus;
enum thread_cmd send_cmd;
enum thread_cmd reply_cmd;
union
{
bool b;
int i;
float f;
const char *str;
void *v;
struct
{
enum rarch_shader_type type;
const char *path;
unsigned index;
} set_shader;
struct
{
float x, y, w, h;
} rect;
struct
{
const uint32_t *data;
unsigned width;
unsigned height;
} image;
struct
{
unsigned index;
bool smooth;
} filtering;
} cmd_data;
struct rarch_viewport vp;
struct rarch_viewport read_vp; // Last viewport reported to caller.
struct
{
slock_t *lock;
uint8_t *buffer;
unsigned width;
unsigned height;
unsigned pitch;
bool updated;
char msg[1024];
} frame;
video_driver_t video_thread;
} 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:
thr->driver_data = thr->driver->init(&thr->info, thr->input, thr->input_data);
thr->cmd_data.b = thr->driver_data;
thr->driver->viewport_info(thr->driver_data, &thr->vp);
thread_reply(thr, CMD_INIT);
break;
case CMD_FREE:
if (thr->driver_data)
thr->driver->free(thr->driver_data);
thr->driver_data = NULL;
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);
break;
case CMD_READ_VIEWPORT:
{
struct rarch_viewport vp = {0};
thr->driver->viewport_info(thr->driver_data, &vp);
if (memcmp(&vp, &thr->read_vp, sizeof(vp)) == 0) // We can read safely
{
thr->cmd_data.b = thr->driver->read_viewport(thr->driver_data, (uint8_t*)thr->cmd_data.v);
thread_reply(thr, CMD_READ_VIEWPORT);
}
else // Viewport dimensions changed right after main thread read the async value. Cannot read safely.
{
thr->cmd_data.b = false;
thread_reply(thr, CMD_READ_VIEWPORT);
}
break;
}
case CMD_SET_SHADER:
{
bool ret = thr->driver->set_shader(thr->driver_data,
thr->cmd_data.set_shader.type,
thr->cmd_data.set_shader.path,
thr->cmd_data.set_shader.index);
thr->cmd_data.b = ret;
thread_reply(thr, CMD_SET_SHADER);
break;
}
case CMD_ALIVE:
thr->cmd_data.b = thr->driver->alive(thr->driver_data);
thread_reply(thr, CMD_ALIVE);
break;
#ifdef HAVE_OVERLAY
case CMD_OVERLAY_ENABLE:
thr->overlay->enable(thr->driver_data, thr->cmd_data.b);
thread_reply(thr, CMD_OVERLAY_ENABLE);
break;
case CMD_OVERLAY_LOAD:
thr->cmd_data.b = thr->overlay->load(thr->driver_data,
thr->cmd_data.image.data,
thr->cmd_data.image.width,
thr->cmd_data.image.height);
thread_reply(thr, CMD_OVERLAY_LOAD);
break;
case CMD_OVERLAY_TEX_GEOM:
thr->overlay->tex_geom(thr->driver_data,
thr->cmd_data.rect.x,
thr->cmd_data.rect.y,
thr->cmd_data.rect.w,
thr->cmd_data.rect.h);
thread_reply(thr, CMD_OVERLAY_TEX_GEOM);
break;
case CMD_OVERLAY_VERTEX_GEOM:
thr->overlay->vertex_geom(thr->driver_data,
thr->cmd_data.rect.x,
thr->cmd_data.rect.y,
thr->cmd_data.rect.w,
thr->cmd_data.rect.h);
thread_reply(thr, CMD_OVERLAY_VERTEX_GEOM);
break;
case CMD_OVERLAY_FULL_SCREEN:
thr->overlay->full_screen(thr->driver_data, thr->cmd_data.b);
thread_reply(thr, CMD_OVERLAY_FULL_SCREEN);
break;
case CMD_OVERLAY_SET_ALPHA:
thr->overlay->set_alpha(thr->driver_data, thr->cmd_data.f);
thread_reply(thr, CMD_OVERLAY_SET_ALPHA);
break;
#endif
case CMD_POKE_SET_BLEND:
thr->poke->set_blend(thr->driver_data, thr->cmd_data.b);
thread_reply(thr, CMD_POKE_SET_BLEND);
break;
case CMD_POKE_SET_FILTERING:
thr->poke->set_filtering(thr->driver_data,
thr->cmd_data.filtering.index,
thr->cmd_data.filtering.smooth);
thread_reply(thr, CMD_POKE_SET_FILTERING);
break;
#ifdef HAVE_FBO
case CMD_POKE_SET_FBO_STATE:
thr->poke->set_fbo_state(thr->driver_data,
thr->cmd_data.i);
thread_reply(thr, CMD_POKE_SET_FBO_STATE);
break;
case CMD_POKE_GET_FBO_STATE:
thr->cmd_data.i = thr->poke->get_fbo_state(thr->driver_data);
thread_reply(thr, CMD_POKE_GET_FBO_STATE);
break;
#endif
case CMD_POKE_SET_ASPECT_RATIO:
thr->poke->set_aspect_ratio(thr->driver_data,
thr->cmd_data.i);
thread_reply(thr, CMD_POKE_SET_ASPECT_RATIO);
break;
default:
thread_reply(thr, thr->send_cmd);
break;
}
if (updated)
{
slock_lock(thr->frame.lock);
#ifdef HAVE_RGUI
thr->poke->set_rgui_texture(thr->driver_data, thr->rgui_texture);
#endif
if (thr->apply_state_changes)
{
thr->poke->apply_state_changes(thr->driver_data);
thr->apply_state_changes = false;
}
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);
struct rarch_viewport vp = {0};
thr->driver->viewport_info(thr->driver_data, &vp);
slock_lock(thr->lock);
thr->alive = alive;
thr->focus = focus;
thr->frame.updated = false;
thr->vp = vp;
scond_signal(thr->cond_cmd);
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;
if (g_extern.is_paused)
{
thread_send_cmd(thr, CMD_ALIVE);
thread_wait_reply(thr, CMD_ALIVE);
return thr->cmd_data.b;
}
else
{
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;
RARCH_PERFORMANCE_INIT(thread_frame);
RARCH_PERFORMANCE_START(thread_frame);
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;
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';
scond_signal(thr->cond_thread);
slock_unlock(thr->frame.lock);
// If we are going to render menu,
// we'll want to block to avoid stepping menu
// at crazy speeds.
#ifdef HAVE_RGUI
if (thr->rgui_texture)
{
while (thr->frame.updated)
scond_wait(thr->cond_cmd, thr->lock);
}
#endif
}
slock_unlock(thr->lock);
RARCH_PERFORMANCE_STOP(thread_frame);
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;
size_t max_size = info->input_scale * RARCH_SCALE_BASE;
max_size *= max_size;
max_size *= info->rgb32 ? sizeof(uint32_t) : sizeof(uint16_t);
thr->frame.buffer = (uint8_t*)malloc(max_size);
if (!thr->frame.buffer)
return false;
memset(thr->frame.buffer, 0x80, max_size);
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 bool thread_set_shader(void *data, enum rarch_shader_type type, const char *path, unsigned index)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.set_shader.type = type;
thr->cmd_data.set_shader.path = path;
thr->cmd_data.set_shader.index = index;
thread_send_cmd(thr, CMD_SET_SHADER);
thread_wait_reply(thr, CMD_SET_SHADER);
return thr->cmd_data.b;
}
static void thread_set_rotation(void *data, unsigned rotation)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.i = rotation;
thread_send_cmd(thr, CMD_SET_ROTATION);
thread_wait_reply(thr, CMD_SET_ROTATION);
}
// This value is set async as stalling on the video driver for every query is too slow.
// This means this value might not be correct, so viewport reads are not supported for now.
static void thread_viewport_info(void *data, struct rarch_viewport *vp)
{
thread_video_t *thr = (thread_video_t*)data;
slock_lock(thr->lock);
*vp = thr->vp;
// Explicitly mem-copied so we can use memcmp correctly later.
memcpy(&thr->read_vp, &thr->vp, sizeof(thr->vp));
slock_unlock(thr->lock);
}
static bool thread_read_viewport(void *data, uint8_t *buffer)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.v = buffer;
thread_send_cmd(thr, CMD_READ_VIEWPORT);
thread_wait_reply(thr, CMD_READ_VIEWPORT);
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);
}
#ifdef HAVE_OVERLAY
static void thread_overlay_enable(void *data, bool state)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.b = state;
thread_send_cmd(thr, CMD_OVERLAY_ENABLE);
thread_wait_reply(thr, CMD_OVERLAY_ENABLE);
}
static bool thread_overlay_load(void *data, const uint32_t *image, unsigned width, unsigned height)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.image.data = image;
thr->cmd_data.image.width = width;
thr->cmd_data.image.height = height;
thread_send_cmd(thr, CMD_OVERLAY_LOAD);
thread_wait_reply(thr, CMD_OVERLAY_LOAD);
return thr->cmd_data.b;
}
static void thread_overlay_tex_geom(void *data, float x, float y, float w, float h)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.rect.x = x;
thr->cmd_data.rect.y = y;
thr->cmd_data.rect.w = w;
thr->cmd_data.rect.h = h;
thread_send_cmd(thr, CMD_OVERLAY_TEX_GEOM);
thread_wait_reply(thr, CMD_OVERLAY_TEX_GEOM);
}
static void thread_overlay_vertex_geom(void *data, float x, float y, float w, float h)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.rect.x = x;
thr->cmd_data.rect.y = y;
thr->cmd_data.rect.w = w;
thr->cmd_data.rect.h = h;
thread_send_cmd(thr, CMD_OVERLAY_VERTEX_GEOM);
thread_wait_reply(thr, CMD_OVERLAY_VERTEX_GEOM);
}
static void thread_overlay_full_screen(void *data, bool enable)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.b = enable;
thread_send_cmd(thr, CMD_OVERLAY_FULL_SCREEN);
thread_wait_reply(thr, CMD_OVERLAY_FULL_SCREEN);
}
static void thread_overlay_set_alpha(void *data, float mod)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.f = mod;
thread_send_cmd(thr, CMD_OVERLAY_SET_ALPHA);
thread_wait_reply(thr, CMD_OVERLAY_SET_ALPHA);
}
static const video_overlay_interface_t thread_overlay = {
thread_overlay_enable,
thread_overlay_load,
thread_overlay_tex_geom,
thread_overlay_vertex_geom,
thread_overlay_full_screen,
thread_overlay_set_alpha,
};
static void thread_get_overlay_interface(void *data, const video_overlay_interface_t **iface)
{
thread_video_t *thr = (thread_video_t*)data;
*iface = &thread_overlay;
thr->driver->overlay_interface(thr->driver_data, &thr->overlay);
}
#endif
static void thread_set_blend(void *data, bool enable)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.b = enable;
thread_send_cmd(thr, CMD_POKE_SET_BLEND);
thread_wait_reply(thr, CMD_POKE_SET_BLEND);
}
static void thread_set_filtering(void *data, unsigned index, bool smooth)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.filtering.index = index;
thr->cmd_data.filtering.smooth = smooth;
thread_send_cmd(thr, CMD_POKE_SET_FILTERING);
thread_wait_reply(thr, CMD_POKE_SET_FILTERING);
}
#ifdef HAVE_FBO
static void thread_set_fbo_state(void *data, unsigned state)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.i = state;
thread_send_cmd(thr, CMD_POKE_SET_FBO_STATE);
thread_wait_reply(thr, CMD_POKE_SET_FBO_STATE);
}
static unsigned thread_get_fbo_state(void *data)
{
thread_video_t *thr = (thread_video_t*)data;
thread_send_cmd(thr, CMD_POKE_GET_FBO_STATE);
thread_wait_reply(thr, CMD_POKE_GET_FBO_STATE);
return thr->cmd_data.i;
}
#endif
static void thread_set_aspect_ratio(void *data, unsigned aspectratio_index)
{
thread_video_t *thr = (thread_video_t*)data;
thr->cmd_data.i = aspectratio_index;
thread_send_cmd(thr, CMD_POKE_SET_ASPECT_RATIO);
thread_wait_reply(thr, CMD_POKE_SET_ASPECT_RATIO);
}
#ifdef HAVE_RGUI
static void thread_set_rgui_texture(void *data, const void *frame)
{
thread_video_t *thr = (thread_video_t*)data;
slock_lock(thr->frame.lock);
thr->rgui_texture = frame;
slock_unlock(thr->frame.lock);
}
#endif
static void thread_apply_state_changes(void *data)
{
thread_video_t *thr = (thread_video_t*)data;
slock_lock(thr->frame.lock);
thr->apply_state_changes = true;
slock_unlock(thr->frame.lock);
}
static const video_poke_interface_t thread_poke = {
thread_set_blend,
thread_set_filtering,
#ifdef HAVE_FBO
thread_set_fbo_state,
thread_get_fbo_state,
NULL,
NULL,
#endif
thread_set_aspect_ratio,
thread_apply_state_changes,
#ifdef HAVE_RGUI
thread_set_rgui_texture,
#endif
};
static void thread_get_poke_interface(void *data, const video_poke_interface_t **iface)
{
thread_video_t *thr = (thread_video_t*)data;
*iface = &thread_poke;
thr->driver->poke_interface(thr->driver_data, &thr->poke);
}
#if defined(HAVE_RMENU) || defined(HAVE_RGUI)
// all stubs for now, might not have to implement them unless we want to port this to consoles
static void thread_start(void) {}
static void thread_stop(void) {}
static void thread_restart(void) {}
#endif
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,
thread_set_shader,
thread_free,
"Thread wrapper",
#if defined(HAVE_RMENU) || defined(HAVE_RGUI)
thread_start,
thread_stop,
thread_restart,
#endif
thread_set_rotation,
thread_viewport_info,
thread_read_viewport,
#ifdef HAVE_OVERLAY
thread_get_overlay_interface, // get_overlay_interface
#endif
thread_get_poke_interface,
};
static void thread_set_callbacks(thread_video_t *thr, const video_driver_t *driver)
{
thr->video_thread = video_thread;
// Disable optional features if not present.
if (!driver->read_viewport)
thr->video_thread.read_viewport = NULL;
if (!driver->set_rotation)
thr->video_thread.set_rotation = NULL;
if (!driver->set_shader)
thr->video_thread.set_shader = NULL;
#ifdef HAVE_OVERLAY
if (!driver->overlay_interface)
thr->video_thread.overlay_interface = NULL;
#endif
// Might have to optionally disable poke_interface features as well.
if (!thr->video_thread.poke_interface)
thr->video_thread.poke_interface = NULL;
}
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;
thread_set_callbacks(thr, driver);
thr->driver = driver;
*out_driver = &thr->video_thread;
*out_data = thr;
return thread_init(thr, info, input, input_data);
}