From 9b1fc098618a5d442711ed516f12c4567039a36e Mon Sep 17 00:00:00 2001 From: Anthony Pesch Date: Sun, 30 Apr 2017 11:11:37 -0400 Subject: [PATCH] remove multitheading code from ta, and add into emulator --- src/emulator.c | 165 +++++++++++++++++++++----------- src/hw/dreamcast.c | 18 +++- src/hw/dreamcast.h | 15 ++- src/hw/pvr/ta.c | 211 ++++++++++++----------------------------- src/hw/pvr/ta.h | 24 ++--- src/hw/pvr/ta_types.h | 1 + src/hw/pvr/tr.c | 2 +- src/hw/pvr/tr.h | 2 +- src/video/gl_backend.c | 3 +- test/test_sh4.c | 2 +- 10 files changed, 219 insertions(+), 224 deletions(-) diff --git a/src/emulator.c b/src/emulator.c index 2248eac3..7602b0af 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -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(); } diff --git a/src/hw/dreamcast.c b/src/hw/dreamcast.c index a68bd943..6bb2ed2d 100644 --- a/src/hw/dreamcast.c +++ b/src/hw/dreamcast.c @@ -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); diff --git a/src/hw/dreamcast.h b/src/hw/dreamcast.h index 0a43802c..04ea219e 100644 --- a/src/hw/dreamcast.h +++ b/src/hw/dreamcast.h @@ -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 diff --git a/src/hw/pvr/ta.c b/src/hw/pvr/ta.c index 7a2a7009..c2b42afa 100644 --- a/src/hw/pvr/ta.c +++ b/src/hw/pvr/ta.c @@ -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; } diff --git a/src/hw/pvr/ta.h b/src/hw/pvr/ta.h index b02d18e2..d78bb006 100644 --- a/src/hw/pvr/ta.h +++ b/src/hw/pvr/ta.h @@ -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 diff --git a/src/hw/pvr/ta_types.h b/src/hw/pvr/ta_types.h index 85b39701..b5e8c96f 100644 --- a/src/hw/pvr/ta_types.h +++ b/src/hw/pvr/ta_types.h @@ -439,6 +439,7 @@ union vert_param { struct tile_ctx { uint32_t addr; + void *userdata; /* pvr / ta state */ unsigned frame; diff --git a/src/hw/pvr/tr.c b/src/hw/pvr/tr.c index 8ab7b62e..56503434 100644 --- a/src/hw/pvr/tr.c +++ b/src/hw/pvr/tr.c @@ -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 */ diff --git a/src/hw/pvr/tr.h b/src/hw/pvr/tr.h index 6516ee9a..4b7a7f44 100644 --- a/src/hw/pvr/tr.h +++ b/src/hw/pvr/tr.h @@ -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); }; diff --git a/src/video/gl_backend.c b/src/video/gl_backend.c index 997aed51..e4761376 100644 --- a/src/video/gl_backend.c +++ b/src/video/gl_backend.c @@ -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]; diff --git a/test/test_sh4.c b/test/test_sh4.c index 4df97606..272c0d50 100644 --- a/test/test_sh4.c +++ b/test/test_sh4.c @@ -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 */