remove multitheading code from ta, and add into emulator

This commit is contained in:
Anthony Pesch 2017-04-30 11:11:37 -04:00
parent 824be359eb
commit 9b1fc09861
10 changed files with 219 additions and 224 deletions

View File

@ -49,6 +49,11 @@ struct emu {
volatile int running;
int debug_menu;
/* last tile context submitted by the dreamcast to be rendered */
mutex_t pending_mutex;
cond_t pending_cond;
struct tile_ctx *pending_ctx;
/* pool of offscreen framebuffers used for rendering the video display */
mutex_t frames_mutex;
struct frame frames[MAX_FRAMES];
@ -96,6 +101,43 @@ static int emu_launch_gdi(struct emu *emu, const char *path) {
return 1;
}
/*
* multithreaded, offscreen video rendering
*/
static void emu_cancel_render(struct emu *emu) {
mutex_lock(emu->pending_mutex);
emu->pending_ctx = NULL;
cond_signal(emu->pending_cond);
mutex_unlock(emu->pending_mutex);
}
static void emu_finish_render(void *userdata) {
struct emu *emu = userdata;
/* ideally, the video thread has parsed the pending context, uploaded its
textures, etc. during the estimated render time. however, if it hasn't
finished, the emulation thread must be paused to avoid altering
the yet-to-be-uploaded texture memory */
mutex_lock(emu->pending_mutex);
emu->pending_ctx = NULL;
mutex_unlock(emu->pending_mutex);
}
static void emu_start_render(void *userdata, struct tile_ctx *ctx) {
struct emu *emu = userdata;
mutex_lock(emu->pending_mutex);
emu->pending_ctx = ctx;
cond_signal(emu->pending_cond);
mutex_unlock(emu->pending_mutex);
}
static struct frame *emu_pop_frame(struct emu *emu) {
mutex_lock(emu->frames_mutex);
@ -178,6 +220,66 @@ static void emu_create_frames(struct emu *emu, struct render_backend *r) {
}
}
static void *emu_video_thread(void *data) {
struct emu *emu = data;
/* create additional renderer on this thread for rendering the tile contexts
to offscreen framebuffers */
struct render_backend *r = r_create_from(emu->r);
struct tr *tr = tr_create(r, ta_texture_provider(emu->dc->ta));
struct tile_ctx *pending_ctx;
struct tile_render_context *rc = malloc(sizeof(struct tile_render_context));
emu_create_frames(emu, r);
while (emu->running) {
/* wait for the next tile context provided by emu_start_render */
mutex_lock(emu->pending_mutex);
if (!emu->pending_ctx) {
cond_wait(emu->pending_cond, emu->pending_mutex);
/* exit thread if shutting down */
if (!emu->pending_ctx) {
continue;
}
}
/* parse the context, uploading its textures to the render backend */
tr_parse_context(tr, emu->pending_ctx, rc);
/* after the context has been parsed, release the mutex to let
emu_finish_render complete */
mutex_unlock(emu->pending_mutex);
/* render the context to the first free framebuffer */
struct frame *frame = emu_alloc_frame(emu, r);
r_bind_framebuffer(r, frame->fb);
r_clear_viewport(r);
tr_render_context(tr, rc);
/* insert fence for main thread to synchronize on in order to ensure that
the context has completely rendered */
frame->fb_sync = r_insert_sync(r);
/* push frame to the presentation queue for the main thread */
emu_push_front_frame(emu, frame);
/* update frame-based profiler stats */
prof_flip();
}
emu_destroy_frames(emu, r);
free(rc);
tr_destroy(tr);
r_destroy(r);
return NULL;
}
static void emu_keydown(void *data, int device_index, enum keycode code,
int16_t value) {
struct emu *emu = data;
@ -331,58 +433,6 @@ static void emu_paint(struct emu *emu) {
}
}
static void *emu_video_thread(void *data) {
struct emu *emu = data;
/* create additional renderer on this thread for rendering the tile contexts
to offscreen framebuffers */
struct render_backend *r = r_create_from(emu->r);
struct tr *tr = tr_create(r, ta_texture_provider(emu->dc->ta));
struct tile_ctx *pending_ctx;
struct tile_render_context *rc = malloc(sizeof(struct tile_render_context));
emu_create_frames(emu, r);
while (emu->running) {
/* wait for the main thread to publish the next ta context to be rendered */
if (!ta_lock_pending_context(emu->dc->ta, &pending_ctx, 1000)) {
continue;
}
/* parse the context, uploading textures it uses to the render backend */
tr_parse_context(tr, pending_ctx, rc);
/* after uploading the textures, unlock to let the main thread resume */
ta_unlock_pending_context(emu->dc->ta);
/* render the context to the first free framebuffer */
struct frame *frame = emu_alloc_frame(emu, r);
r_bind_framebuffer(r, frame->fb);
r_clear_viewport(r);
tr_render_context(tr, rc);
/* insert fence for main thread to synchronize on in order to ensure that
the context has completely rendered */
frame->fb_sync = r_insert_sync(r);
/* push frame to the presentation queue for the main thread */
emu_push_front_frame(emu, frame);
/* update frame-based profiler stats */
prof_flip();
}
emu_destroy_frames(emu, r);
free(rc);
tr_destroy(tr);
r_destroy(r);
return NULL;
}
void emu_run(struct emu *emu, const char *path) {
/* load gdi / bin if specified */
if (path) {
@ -460,6 +510,7 @@ void emu_run(struct emu *emu, const char *path) {
/* wait for video thread to exit */
void *result;
emu_cancel_render(emu);
thread_join(video_thread, &result);
}
@ -474,6 +525,8 @@ void emu_destroy(struct emu *emu) {
/* destroy render backend */
{
mutex_destroy(emu->frames_mutex);
cond_destroy(emu->pending_cond);
mutex_destroy(emu->pending_mutex);
nk_destroy(emu->nk);
mp_destroy(emu->mp);
r_destroy(emu->r);
@ -498,13 +551,19 @@ struct emu *emu_create(struct window *win) {
win_add_listener(emu->win, &emu->listener);
/* create dreamcast */
emu->dc = dc_create();
struct dreamcast_client client;
client.userdata = emu;
client.start_render = &emu_start_render;
client.finish_render = &emu_finish_render;
emu->dc = dc_create(&client);
/* create render backend */
{
emu->r = r_create(emu->win);
emu->mp = mp_create(emu->win, emu->r);
emu->nk = nk_create(emu->win, emu->r);
emu->pending_mutex = mutex_create();
emu->pending_cond = cond_create();
emu->frames_mutex = mutex_create();
}

View File

@ -17,6 +17,18 @@
DEFINE_OPTION_INT(gdb, 0, "Run gdb debug server");
void dc_finish_render(struct dreamcast *dc) {
if (dc->client.finish_render) {
dc->client.finish_render(dc->client.userdata);
}
}
void dc_start_render(struct dreamcast *dc, struct tile_ctx *ctx) {
if (dc->client.start_render) {
dc->client.start_render(dc->client.userdata, ctx);
}
}
void dc_joy_remove(struct dreamcast *dc, int joystick_index) {
list_for_each_entry(dev, &dc->devices, struct device, it) {
if (dev->window_if && dev->window_if->joy_remove) {
@ -192,9 +204,13 @@ void dc_destroy(struct dreamcast *dc) {
free(dc);
}
struct dreamcast *dc_create() {
struct dreamcast *dc_create(const struct dreamcast_client *client) {
struct dreamcast *dc = calloc(1, sizeof(struct dreamcast));
if (client) {
dc->client = *client;
}
dc->debugger = OPTION_gdb ? debugger_create(dc) : NULL;
dc->memory = memory_create(dc);
dc->scheduler = scheduler_create(dc);

View File

@ -23,6 +23,7 @@ struct pvr;
struct scheduler;
struct sh4;
struct ta;
struct tile_ctx;
/*
* register callbacks
@ -132,7 +133,17 @@ struct device {
/*
* machine
*/
typedef void (*start_render_cb)(void *, struct tile_ctx *);
typedef void (*finish_render_cb)(void *);
struct dreamcast_client {
void *userdata;
start_render_cb start_render;
finish_render_cb finish_render;
};
struct dreamcast {
struct dreamcast_client client;
struct debugger *debugger;
struct memory *memory;
struct scheduler *scheduler;
@ -150,7 +161,7 @@ struct dreamcast {
struct list devices;
};
struct dreamcast *dc_create();
struct dreamcast *dc_create(const struct dreamcast_client *client);
void dc_destroy(struct dreamcast *dc);
void *dc_create_device(struct dreamcast *dc, size_t size, const char *name,
@ -180,5 +191,7 @@ void dc_keydown(struct dreamcast *dc, int device_index, enum keycode code,
int16_t value);
void dc_joy_add(struct dreamcast *dc, int joystick_index);
void dc_joy_remove(struct dreamcast *dc, int joystick_index);
void dc_start_render(struct dreamcast *dc, struct tile_ctx *ctx);
void dc_finish_render(struct dreamcast *dc);
#endif

View File

@ -10,7 +10,6 @@
#include "hw/sh4/sh4.h"
#include "sys/exception_handler.h"
#include "sys/filesystem.h"
#include "sys/thread.h"
#include "ui/nuklear.h"
DEFINE_AGGREGATE_COUNTER(ta_data);
@ -38,6 +37,7 @@ struct ta {
struct device;
struct texture_provider provider;
uint8_t *video_ram;
struct trace_writer *trace_writer;
/* yuv data converter state */
uint8_t *yuv_data;
@ -52,24 +52,12 @@ struct ta {
struct list live_contexts;
struct tile_ctx *curr_context;
/* texture cache entry pool */
struct ta_texture_entry entries[8192];
struct list free_entries;
struct rb_tree live_entries;
/* texture cache state */
unsigned frame;
int num_textures;
/* each time the STARTRENDER register is written to, the current TA param
buffer and PVR register state required to render the params is saved to
this "pending context". the pending context is not actually rendered on
the emulation thread, instead it's made available to the graphics thread
through ta_lock_pending_context and ta_unlock_pending_context functions.
please read through the comments there, as well as the comments in
ta_start_render, ta_render_timer and ta_end_render to understand how
access to this context as well as the texture data it depends on is
synchronized */
struct tile_ctx *pending_context;
/* textures for the pending context are uploaded to the render backend by
the graphics thread in parallel to the main emulation thread executing,
/* textures for the current context are uploaded to the render backend by
the video thread in parallel to the main emulation thread executing,
which may erroneously write to a texture before receiving the end of
render interrupts. in order to avoid race conditions around the texture's
dirty state in these situations, textures are not immediately marked dirty
@ -78,16 +66,9 @@ struct ta {
struct list invalidated_entries;
int num_invalidated;
/* primitives used for synchronizing access to the pending context and any
invalidated textures between the graphics and emulation threads */
mutex_t pending_mutex;
cond_t pending_cond;
/* debug info */
unsigned frame;
int frames_skipped;
int num_textures;
struct trace_writer *trace_writer;
struct ta_texture_entry entries[8192];
struct list free_entries;
struct rb_tree live_entries;
};
int g_param_sizes[0x100 * TA_NUM_PARAMS * TA_NUM_VERTS];
@ -328,24 +309,6 @@ static struct ta_texture_entry *ta_find_texture(struct ta *ta, union tsp tsp,
live_it, &ta_entry_cb);
}
static struct texture_entry *ta_texture_provider_find_texture(void *data,
union tsp tsp,
union tcw tcw) {
struct ta *ta = (struct ta *)data;
struct ta_texture_entry *entry = ta_find_texture(ta, tsp, tcw);
if (!entry) {
return NULL;
}
/* sanity check that the texture source is valid for the current frame. video
ram will be modified between frames, if these values don't match something
is broken in the thread synchronization */
CHECK_EQ(entry->frame, ta->frame);
return (struct texture_entry *)entry;
}
static struct tile_ctx *ta_get_context(struct ta *ta, uint32_t addr) {
list_for_each_entry(ctx, &ta->live_contexts, struct tile_ctx, it) {
if (ctx->addr == addr) {
@ -542,14 +505,11 @@ static void ta_register_texture_source(struct ta *ta, union tsp tsp,
}
}
static void ta_register_texture_sources(struct ta *ta, struct tile_ctx *ctx,
int *num_polys) {
static void ta_register_texture_sources(struct ta *ta, struct tile_ctx *ctx) {
const uint8_t *data = ctx->params;
const uint8_t *end = ctx->params + ctx->size;
int vertex_type = 0;
*num_polys = 0;
while (data < end) {
union pcw pcw = *(union pcw *)data;
@ -563,8 +523,6 @@ static void ta_register_texture_sources(struct ta *ta, struct tile_ctx *ctx,
if (param->type0.pcw.texture) {
ta_register_texture_source(ta, param->type0.tsp, param->type0.tcw);
}
(*num_polys)++;
} break;
default:
@ -671,83 +629,63 @@ static void ta_save_state(struct ta *ta, struct tile_ctx *ctx) {
}
}
static void ta_end_render(struct ta *ta) {
static void ta_finish_render(void *data) {
struct tile_ctx *ctx = data;
struct ta *ta = ctx->userdata;
/* ensure the client has finished rendering */
dc_finish_render(ta->dc);
/* texture entries are only valid between each start / finish render pair,
increment frame number again to invalidate */
ta->frame++;
/* return context back to pool */
ta_free_context(ta, ctx);
/* let the game know rendering is complete */
holly_raise_interrupt(ta->holly, HOLLY_INTC_PCEOVINT);
holly_raise_interrupt(ta->holly, HOLLY_INTC_PCEOIINT);
holly_raise_interrupt(ta->holly, HOLLY_INTC_PCEOTINT);
}
static void ta_render_timer(void *data) {
struct ta *ta = data;
/* ideally, the graphics thread has parsed the pending context, uploaded its
textures, etc. during the estimated render time. however, if it hasn't
finished, the emulation thread must be paused to avoid altering
the yet-to-be-uploaded texture memory */
mutex_lock(ta->pending_mutex);
/* if the graphics thread didn't actually attempt to parse the frame, which
often happens when running unthrottled, skip it */
if (ta->pending_context) {
ta_free_context(ta, ta->pending_context);
ta->pending_context = NULL;
ta->frames_skipped++;
}
/* texture entries are only valid between each start / end render, increment
frame number again to invalidate */
ta->frame++;
mutex_unlock(ta->pending_mutex);
ta_end_render(ta);
}
static void ta_start_render(struct ta *ta, struct tile_ctx *ctx) {
prof_counter_add(COUNTER_ta_renders, 1);
mutex_lock(ta->pending_mutex);
/* now that access to texture data is locked, mark any textures dirty
that were invalidated by a memory watch on the emulation thread */
ta_dirty_invalidated_textures(ta);
/* remove context from pool */
ta_unlink_context(ta, ctx);
/* increment internal frame number. this frame number is assigned to the
/* incement internal frame number. this frame number is assigned to the
context and each texture source it registers to assert synchronization
between the emulator and graphics thread is working as expected */
between the emulator and video thread is working as expected */
ta->frame++;
/* now that the video thread is sure to not be accessing the texture data,
mark any textures dirty that were invalidated by a memory watch */
ta_dirty_invalidated_textures(ta);
/* register the source of each texture referenced by the context with the
tile renderer. note, uploading the texture to the render backend happens
lazily while rendering the context. this registration just lets the
backend know where the texture's source data is */
ta_register_texture_sources(ta, ctx);
/* save off required state that may be modified by the time the context is
rendered */
ta_save_state(ta, ctx);
/* register the source of each texture referenced by the context with the
tile renderer. note, the process of actually uploading the texture to the
render backend happens lazily while rendering the context (keeping all
backend operations on the same thread). this registration just lets the
backend know where the texture's source data is */
int num_polys = 0;
ta_register_texture_sources(ta, ctx, &num_polys);
/* let the client know to start rendering the context */
dc_start_render(ta->dc, ctx);
/* give each frame 10 ms to finish rendering
TODO figure out a heuristic involving the number of polygons rendered */
int64_t ns = INT64_C(10000000);
scheduler_start_timer(ta->scheduler, &ta_render_timer, ta, ns);
int64_t end = INT64_C(10000000);
ctx->userdata = ta;
scheduler_start_timer(ta->scheduler, &ta_finish_render, ctx, end);
if (ta->trace_writer) {
trace_writer_render_context(ta->trace_writer, ctx);
}
/* signal to the graphics thread that a new frame is available to be parsed */
CHECK(ta->pending_context == NULL);
ta->pending_context = ctx;
cond_signal(ta->pending_cond);
mutex_unlock(ta->pending_mutex);
}
static void ta_yuv_init(struct ta *ta) {
@ -936,21 +874,6 @@ static void ta_toggle_tracing(struct ta *ta) {
}
}
static void ta_debug_menu_toggle_tracing(struct ta *ta) {
/* this is called from the graphics thread. need to take the context lock as
the emulation thread may be modifying the pending context, registering
textures for it, etc. */
mutex_lock(ta->pending_mutex);
ta_toggle_tracing(ta);
mutex_unlock(ta->pending_mutex);
}
static void ta_debug_menu_clear_textures(struct ta *ta) {
mutex_lock(ta->pending_mutex);
ta_clear_textures(ta);
mutex_unlock(ta->pending_mutex);
}
static void ta_debug_menu(struct device *dev, struct nk_context *ctx) {
struct ta *ta = (struct ta *)dev;
@ -959,17 +882,16 @@ static void ta_debug_menu(struct device *dev, struct nk_context *ctx) {
if (nk_menu_begin_label(ctx, "TA", NK_TEXT_LEFT, nk_vec2(140.0f, 200.0f))) {
nk_layout_row_dynamic(ctx, DEBUG_MENU_HEIGHT, 1);
nk_value_int(ctx, "frames skipped", ta->frames_skipped);
nk_value_int(ctx, "num textures", ta->num_textures);
if (!ta->trace_writer && nk_button_label(ctx, "start trace")) {
ta_debug_menu_toggle_tracing(ta);
ta_toggle_tracing(ta);
} else if (ta->trace_writer && nk_button_label(ctx, "stop trace")) {
ta_debug_menu_toggle_tracing(ta);
ta_toggle_tracing(ta);
}
if (nk_button_label(ctx, "clear texture cache")) {
ta_debug_menu_clear_textures(ta);
ta_clear_textures(ta);
}
nk_menu_end(ctx);
@ -1016,44 +938,33 @@ void ta_build_tables() {
}
}
void ta_unlock_pending_context(struct ta *ta) {
/* after the pending mutex is released, this context is no longer valid
as the emulation thread will resume */
ta_free_context(ta, ta->pending_context);
ta->pending_context = NULL;
static struct texture_entry *ta_texture_provider_find_texture(void *data,
union tsp tsp,
union tcw tcw) {
struct ta *ta = (struct ta *)data;
struct ta_texture_entry *entry = ta_find_texture(ta, tsp, tcw);
mutex_unlock(ta->pending_mutex);
}
int ta_lock_pending_context(struct ta *ta, struct tile_ctx **pending_ctx,
int wait_ms) {
mutex_lock(ta->pending_mutex);
/* wait for the next available context */
if (!ta->pending_context) {
int res = cond_timedwait(ta->pending_cond, ta->pending_mutex, wait_ms);
if (!res || !ta->pending_context) {
mutex_unlock(ta->pending_mutex);
return 0;
}
if (!entry) {
return NULL;
}
/* sanity check the context is from the current frame */
CHECK_EQ(ta->pending_context->frame, ta->frame);
/* sanity check that the texture source is valid for the current frame. video
ram will be modified between frames, if these values don't match something
is broken in the thread synchronization */
CHECK_EQ(entry->frame, ta->frame);
*pending_ctx = ta->pending_context;
return 1;
return (struct texture_entry *)entry;
}
struct texture_provider *ta_texture_provider(struct ta *ta) {
if (!ta->provider.userdata) {
ta->provider.userdata = ta;
ta->provider.find_texture = &ta_texture_provider_find_texture;
}
return &ta->provider;
}
void ta_destroy(struct ta *ta) {
cond_destroy(ta->pending_cond);
mutex_destroy(ta->pending_mutex);
dc_destroy_window_interface(ta->window_if);
dc_destroy_device((struct device *)ta);
}
@ -1065,8 +976,6 @@ struct ta *ta_create(struct dreamcast *dc) {
ta->window_if = dc_create_window_interface(&ta_debug_menu, NULL, NULL, NULL);
ta->provider =
(struct texture_provider){ta, &ta_texture_provider_find_texture};
ta->pending_mutex = mutex_create();
ta->pending_cond = cond_create();
return ta;
}

View File

@ -6,14 +6,24 @@
#include "hw/pvr/ta_types.h"
struct dreamcast;
struct ta;
struct texture_provider;
DECLARE_COUNTER(ta_renders);
AM_DECLARE(ta_fifo_map);
#define TA_CODEBOOK_SIZE (256 * 8)
extern int g_param_sizes[0x100 * TA_NUM_PARAMS * TA_NUM_VERTS];
extern int g_poly_types[0x100 * TA_NUM_PARAMS * TA_NUM_LISTS];
extern int g_vertex_types[0x100 * TA_NUM_PARAMS * TA_NUM_LISTS];
void ta_build_tables();
struct ta *ta_create(struct dreamcast *dc);
void ta_destroy(struct ta *ta);
static inline int ta_get_param_size(union pcw pcw, int vertex_type) {
return g_param_sizes[pcw.obj_control * TA_NUM_PARAMS * TA_NUM_VERTS +
pcw.para_type * TA_NUM_VERTS + vertex_type];
@ -94,20 +104,6 @@ static inline int ta_texture_size(union tsp tsp, union tcw tcw) {
return texture_size;
}
struct ta;
void ta_build_tables();
DECLARE_COUNTER(ta_renders);
AM_DECLARE(ta_fifo_map);
struct ta *ta_create(struct dreamcast *dc);
void ta_destroy(struct ta *ta);
struct texture_provider *ta_texture_provider(struct ta *ta);
int ta_lock_pending_context(struct ta *ta, struct tile_ctx **pending_ctx,
int wait_ms);
void ta_unlock_pending_context(struct ta *ta);
#endif

View File

@ -439,6 +439,7 @@ union vert_param {
struct tile_ctx {
uint32_t addr;
void *userdata;
/* pvr / ta state */
unsigned frame;

View File

@ -128,7 +128,7 @@ static texture_handle_t tr_demand_texture(struct tr *tr,
texture generation */
struct texture_entry *entry =
tr->provider->find_texture(tr->provider->data, tsp, tcw);
tr->provider->find_texture(tr->provider->userdata, tsp, tcw);
CHECK_NOTNULL(entry);
/* if there's a non-dirty handle, return it */

View File

@ -38,7 +38,7 @@ struct texture_entry {
emulating the actual ta, textures will be provided from guest memory, but
when playing back traces the textures will come from the trace itself */
struct texture_provider {
void *data;
void *userdata;
struct texture_entry *(*find_texture)(void *, union tsp, union tcw);
};

View File

@ -724,7 +724,8 @@ sync_handle_t r_insert_sync(struct render_backend *r) {
}
void r_destroy_texture(struct render_backend *r, texture_handle_t handle) {
/* lookup texture entry */
/* lookup texture entry
FIXME need common hashtable */
int entry;
for (entry = 0; entry < MAX_TEXTURES; entry++) {
struct texture *tex = &r->textures[entry];

View File

@ -127,7 +127,7 @@ static void run_sh4_test(struct dreamcast *dc, const struct sh4_test *test) {
}
TEST(sh4_x64) {
struct dreamcast *dc = dc_create();
struct dreamcast *dc = dc_create(NULL);
CHECK_NOTNULL(dc);
/* clang-format off */