diff --git a/src/emulator.c b/src/emulator.c index 31f275d6..801076da 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -120,13 +120,7 @@ struct emu { list which will be processed the next time the threads are synchronized */ struct list modified_textures; - /* debug stats */ - int frame_stats; - float swap_times[360]; - int64_t last_swap; - /* debugging */ - int debug_menu; struct trace_writer *trace_writer; }; @@ -469,102 +463,6 @@ static void emu_set_aspect_ratio(struct emu *emu, const char *new_ratio) { emu->aspect_ratio = i; } -static void emu_debug_menu(struct emu *emu) { -#ifdef HAVE_IMGUI - if (!emu->debug_menu) { - return; - } - - if (igBeginMainMenuBar()) { - if (igBeginMenu("EMU", 1)) { - if (igMenuItem("frame stats", NULL, emu->frame_stats, 1)) { - emu->frame_stats = !emu->frame_stats; - } - if (!emu->trace_writer && igMenuItem("start trace", NULL, 0, 1)) { - emu_start_tracing(emu); - } - if (emu->trace_writer && igMenuItem("stop trace", NULL, 1, 1)) { - emu_stop_tracing(emu); - } - if (igMenuItem("clear texture cache", NULL, 0, 1)) { - emu_dirty_textures(emu); - } - igEndMenu(); - } - - igEndMainMenuBar(); - } - - holly_debug_menu(emu->dc->holly); - aica_debug_menu(emu->dc->aica); - arm7_debug_menu(emu->dc->arm7); - sh4_debug_menu(emu->dc->sh4); - - /* add status */ - if (igBeginMainMenuBar()) { - char status[128]; - int frames = (int)prof_counter_load(COUNTER_frames); - int ta_renders = (int)prof_counter_load(COUNTER_ta_renders); - int pvr_vblanks = (int)prof_counter_load(COUNTER_pvr_vblanks); - int sh4_instrs = (int)(prof_counter_load(COUNTER_sh4_instrs) / 1000000.0f); - int arm7_instrs = - (int)(prof_counter_load(COUNTER_arm7_instrs) / 1000000.0f); - - snprintf(status, sizeof(status), "FPS %3d RPS %3d VBS %3d SH4 %4d ARM %d", - frames, ta_renders, pvr_vblanks, sh4_instrs, arm7_instrs); - - /* right align */ - struct ImVec2 content; - struct ImVec2 size; - igGetContentRegionMax(&content); - igCalcTextSize(&size, status, NULL, 0, 0.0f); - igSetCursorPosX(content.x - size.x); - igText(status); - - igEndMainMenuBar(); - } - - if (emu->frame_stats) { - bool opened = true; - - if (igBegin("frame stats", &opened, ImGuiWindowFlags_AlwaysAutoResize)) { - /* memory accesses */ - { - igValueInt("mmio reads", (int)prof_counter_load(COUNTER_mmio_read)); - igValueInt("mmio writes", (int)prof_counter_load(COUNTER_mmio_write)); - } - - /* swap times */ - { - struct ImVec2 graph_size = {300.0f, 50.0f}; - int num_swap_times = ARRAY_SIZE(emu->swap_times); - - float min_time = FLT_MAX; - float max_time = -FLT_MAX; - float avg_time = 0.0f; - for (int i = 0; i < num_swap_times; i++) { - float time = emu->swap_times[i]; - min_time = MIN(min_time, time); - max_time = MAX(max_time, time); - avg_time += time; - } - avg_time /= num_swap_times; - - igValueFloat("min swap time", min_time, "%.2f"); - igValueFloat("max swap time", max_time, "%.2f"); - igValueFloat("avg swap time", avg_time, "%.2f"); - igPlotLines("", emu->swap_times, num_swap_times, - emu->frame % num_swap_times, NULL, 0.0f, 60.0f, graph_size, - sizeof(float)); - } - } - igEnd(); - - emu->frame_stats = (int)opened; - } -#endif -} - /* * frame running logic */ @@ -622,9 +520,6 @@ void emu_render_frame(struct emu *emu) { the host will render the ui completely unthrottled */ uint32_t silence[AICA_SAMPLE_FREQ / 60] = {0}; audio_push(emu->host, (int16_t *)silence, ARRAY_SIZE(silence)); - - emu_debug_menu(emu); - return; } @@ -678,20 +573,11 @@ void emu_render_frame(struct emu *emu) { --------------------------------------------------------------------------- | emu_vblank_out sets EMU_ENDFRAME */ - /* ensure the emulation thread isn't still executing a previous frame */ - if (emu->multi_threaded) { - mutex_lock(emu->req_mutex); - mutex_unlock(emu->req_mutex); - } - - /* build the debug menus before running the frame, while the two threads - are synchronized */ - emu_debug_menu(emu); - /* request a frame to be ran */ if (emu->multi_threaded) { mutex_lock(emu->req_mutex); + CHECK_EQ(emu->state, EMU_WAITING); emu->state = EMU_RUNFRAME; cond_signal(emu->req_cond); @@ -744,15 +630,68 @@ void emu_render_frame(struct emu *emu) { and vblank_out at this point, but there's no need to wait for it */ } +void emu_debug_menu(struct emu *emu) { +#ifdef HAVE_IMGUI + /* ensure the emulation thread isn't still executing a previous frame */ + if (emu->multi_threaded) { + mutex_lock(emu->req_mutex); + CHECK_EQ(emu->state, EMU_WAITING); + mutex_unlock(emu->req_mutex); + } + + if (igBeginMainMenuBar()) { + if (igBeginMenu("EMU", 1)) { + if (igMenuItem("clear texture cache", NULL, 0, 1)) { + emu_dirty_textures(emu); + } + if (!emu->trace_writer && igMenuItem("start trace", NULL, 0, 1)) { + emu_start_tracing(emu); + } + if (emu->trace_writer && igMenuItem("stop trace", NULL, 1, 1)) { + emu_stop_tracing(emu); + } + igEndMenu(); + } + + igEndMainMenuBar(); + } + + holly_debug_menu(emu->dc->holly); + aica_debug_menu(emu->dc->aica); + arm7_debug_menu(emu->dc->arm7); + sh4_debug_menu(emu->dc->sh4); + + /* add status */ + if (igBeginMainMenuBar()) { + char status[128]; + int frames = (int)prof_counter_load(COUNTER_frames); + int ta_renders = (int)prof_counter_load(COUNTER_ta_renders); + int pvr_vblanks = (int)prof_counter_load(COUNTER_pvr_vblanks); + int sh4_instrs = (int)(prof_counter_load(COUNTER_sh4_instrs) / 1000000.0f); + int arm7_instrs = + (int)(prof_counter_load(COUNTER_arm7_instrs) / 1000000.0f); + + snprintf(status, sizeof(status), "FPS %3d RPS %3d VBS %3d SH4 %4d ARM %d", + frames, ta_renders, pvr_vblanks, sh4_instrs, arm7_instrs); + + /* right align */ + struct ImVec2 content; + struct ImVec2 size; + igGetContentRegionMax(&content); + igCalcTextSize(&size, status, NULL, 0, 0.0f); + igSetCursorPosX(content.x - size.x); + igText(status); + + igEndMainMenuBar(); + } +#endif +} + int emu_load(struct emu *emu, const char *path) { return dc_load(emu->dc, path); } int emu_keydown(struct emu *emu, int port, int key, uint16_t value) { - if (key == K_F1 && value) { - emu->debug_menu = !emu->debug_menu; - } - if (key >= K_CONT_C && key <= K_CONT_RTRIG) { dc_input(emu->dc, port, key - K_CONT_C, value); } @@ -760,19 +699,6 @@ int emu_keydown(struct emu *emu, int port, int key, uint16_t value) { return 0; } -void emu_vid_swapped(struct emu *emu) { - /* keep track of the time between swaps */ - int64_t now = time_nanoseconds(); - - if (emu->last_swap) { - float swap_time_ms = (float)(now - emu->last_swap) / 1000000.0f; - int num_swap_times = ARRAY_SIZE(emu->swap_times); - emu->swap_times[emu->frame % num_swap_times] = swap_time_ms; - } - - emu->last_swap = now; -} - void emu_vid_destroyed(struct emu *emu) { rb_for_each_entry_safe(tex, &emu->live_textures, struct emu_texture, live_it) { diff --git a/src/emulator.h b/src/emulator.h index e0ed018f..1d58f9f7 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -12,10 +12,10 @@ void emu_destroy(struct emu *emu); void emu_vid_created(struct emu *emu, struct render_backend *r); void emu_vid_destroyed(struct emu *emu); -void emu_vid_swapped(struct emu *emu); int emu_keydown(struct emu *emu, int port, int key, uint16_t value); int emu_load(struct emu *emu, const char *path); +void emu_debug_menu(struct emu *emu); void emu_render_frame(struct emu *emu); #endif diff --git a/src/host/sdl_host.c b/src/host/sdl_host.c index 32f87e54..8354608c 100644 --- a/src/host/sdl_host.c +++ b/src/host/sdl_host.c @@ -57,6 +57,14 @@ struct host { int keymap[K_NUM_KEYS]; SDL_GameController *controllers[INPUT_MAX_CONTROLLERS]; } input; + + struct { + int show_menu; + int show_times; + unsigned frame; + float swap_times[360]; + int64_t last_swap; + } dbg; }; /* @@ -156,8 +164,7 @@ static void audio_destroy_device(struct host *host) { static int audio_create_device(struct host *host) { /* SDL expects the number of buffered frames to be a power of two */ - int target_frames = MS_TO_AUDIO_FRAMES(OPTION_latency); - target_frames = (int)npow2((uint32_t)target_frames); + int target_frames = 1 << 12; /* match AICA output format */ SDL_AudioSpec want; @@ -183,12 +190,6 @@ static int audio_create_device(struct host *host) { return 1; } -static void audio_set_latency(struct host *host, int latency) { - audio_destroy_device(host); - int res = audio_create_device(host); - CHECK(res); -} - void audio_push(struct host *host, const int16_t *data, int num_frames) { if (!host->audio.dev) { return; @@ -214,7 +215,12 @@ static void audio_shutdown(struct host *host) { } static int audio_init(struct host *host) { - if (!OPTION_audio) { + /* reset */ + memset(&host->audio, 0, sizeof(host->audio)); + + /* if the audio sync is disabled, don't actually create a device, as audio + playback won't be possible without resampling the data */ + if (!audio_sync_enabled()) { return 1; } @@ -233,6 +239,11 @@ static int audio_init(struct host *host) { return 1; } +static int audio_restart(struct host *host) { + audio_shutdown(host); + return audio_init(host); +} + /* * video */ @@ -263,9 +274,10 @@ static SDL_GLContext video_create_context(struct host *host) { SDL_GLContext ctx = SDL_GL_CreateContext(host->win); CHECK_NOTNULL(ctx, "video_create_context failed: %s", SDL_GetError()); - /* disable vsync */ - int res = SDL_GL_SetSwapInterval(0); - CHECK_EQ(res, 0, "video_create_context failed to disable vsync"); + /* force vsync */ + int vsync = video_sync_enabled(); + int res = SDL_GL_SetSwapInterval(vsync); + CHECK_EQ(res, 0, "video_create_context failed to set swap interval"); /* link in gl functions at runtime */ res = gladLoadGLLoader((GLADloadproc)&SDL_GL_GetProcAddress); @@ -296,10 +308,18 @@ static void video_shutdown(struct host *host) { } r_destroy(host->video.r); + video_destroy_context(host, host->video.ctx); } static int video_init(struct host *host) { + /* reset */ + memset(&host->video, 0, sizeof(host->video)); + + /* immediately poll window size for platforms like Android where the window + starts fullscreen, ignoring the default width and height */ + SDL_GetWindowSize(host->win, &host->video.width, &host->video.height); + host->video.ctx = video_create_context(host); host->video.r = r_create(host->video.width, host->video.height); @@ -515,6 +535,11 @@ static void input_keydown(struct host *host, int port, int key, mapping is available */ int mapping = host->input.keymap[key]; + if (key == K_F1 && value) { + host->dbg.show_menu = !host->dbg.show_menu; + return; + } + for (int i = 0; i < 2; i++) { if (key == K_UNKNOWN) { break; @@ -579,6 +604,9 @@ static void input_shutdown(struct host *host) { } static int input_init(struct host *host) { + /* reset */ + memset(&host->input, 0, sizeof(host->input)); + /* SDL won't push events for joysticks which are already connected at init */ int num_joysticks = SDL_NumJoysticks(); @@ -623,9 +651,106 @@ int ui_load_game(struct host *host, const char *path) { static void host_swap_window(struct host *host) { SDL_GL_SwapWindow(host->win); - if (host->emu) { - emu_vid_swapped(host->emu); + /* keep track of the time between swaps */ + int64_t now = time_nanoseconds(); + + if (host->dbg.last_swap) { + float swap_time_ms = (float)(now - host->dbg.last_swap) / 1000000.0f; + int num_times = ARRAY_SIZE(host->dbg.swap_times); + host->dbg.swap_times[host->dbg.frame % num_times] = swap_time_ms; } + + host->dbg.last_swap = now; + host->dbg.frame++; +} + +static void host_debug_menu(struct host *host) { +#ifdef HAVE_IMGUI + if (!host->dbg.show_menu) { + return; + } + + if (igBeginMainMenuBar()) { + if (igBeginMenu("HOST", 1)) { + if (igBeginMenu("sync", 1)) { + struct { + const char *desc; + int enabled; + } options[] = { + {"audio", audio_sync_enabled()}, /* */ + {"video", video_sync_enabled()}, /* */ + }; + int num_options = (int)ARRAY_SIZE(options); + + for (int i = 0; i < num_options; i++) { + const char *desc = options[i].desc; + int enabled = options[i].enabled; + + if (igMenuItem(desc, NULL, enabled, 1)) { + int len = strlen(OPTION_sync); + + if (enabled) { + for (int i = 0; i < len; i++) { + if (OPTION_sync[i] == desc[0]) { + OPTION_sync[i] = OPTION_sync[len - 1]; + OPTION_sync[len - 1] = 0; + break; + } + } + } else { + OPTION_sync[len] = desc[0]; + OPTION_sync[len + 1] = 0; + } + + OPTION_sync_dirty = 1; + } + } + + igEndMenu(); + } + + if (igMenuItem("frame times", NULL, host->dbg.show_times, 1)) { + host->dbg.show_times = !host->dbg.show_times; + } + + igEndMenu(); + } + + igEndMainMenuBar(); + } + + if (host->dbg.show_times) { + bool opened = true; + + if (igBegin("frame times", &opened, ImGuiWindowFlags_AlwaysAutoResize)) { + struct ImVec2 graph_size = {300.0f, 50.0f}; + int num_times = ARRAY_SIZE(host->dbg.swap_times); + + float min_time = FLT_MAX; + float max_time = -FLT_MAX; + float avg_time = 0.0f; + for (int i = 0; i < num_times; i++) { + float time = host->dbg.swap_times[i]; + min_time = MIN(min_time, time); + max_time = MAX(max_time, time); + avg_time += time; + } + avg_time /= num_times; + + igValueFloat("min frame time", min_time, "%.2f"); + igValueFloat("max frame time", max_time, "%.2f"); + igValueFloat("avg frame time", avg_time, "%.2f"); + igPlotLines("", host->dbg.swap_times, num_times, + host->dbg.frame % num_times, NULL, 0.0f, 60.0f, graph_size, + sizeof(float)); + } + igEnd(); + + host->dbg.show_times = (int)opened; + } + + emu_debug_menu(host->emu); +#endif } static void host_poll_events(struct host *host) { @@ -794,16 +919,19 @@ static void host_poll_events(struct host *host) { } /* check for option changes at this time as well */ - if (OPTION_latency_dirty) { - audio_set_latency(host, OPTION_latency); - OPTION_latency_dirty = 0; - } - if (OPTION_fullscreen_dirty) { video_set_fullscreen(host, OPTION_fullscreen); OPTION_fullscreen_dirty = 0; } + if (OPTION_sync_dirty) { + int res = audio_restart(host); + CHECK(res, "audio_restart failed"); + res = video_restart(host); + CHECK(res, "video_restart failed"); + OPTION_sync_dirty = 0; + } + /* update reverse button map when optionsc hange */ int dirty_map = 0; @@ -849,10 +977,6 @@ static int host_init(struct host *host) { CHECK_NOTNULL(host->win, "host_create window creation failed: %s", SDL_GetError()); - /* immediately poll window size for platforms like Android where the window - starts fullscreen, ignoring the default width and height */ - SDL_GetWindowSize(host->win, &host->video.width, &host->video.height); - if (!audio_init(host)) { return 0; } @@ -968,6 +1092,7 @@ int main(int argc, char **argv) { imgui_begin_frame(host->imgui); /* render emulator output and build up imgui buffers */ + host_debug_menu(host); emu_render_frame(host->emu); ui_build_menus(host->ui); diff --git a/src/options.c b/src/options.c index 860cfc47..fba2348c 100644 --- a/src/options.c +++ b/src/options.c @@ -2,9 +2,6 @@ #include "core/core.h" #include "host/keycode.h" -const int LATENCY_PRESETS[] = {45, 90, 180}; -const int NUM_LATENCY_PRESETS = ARRAY_SIZE(LATENCY_PRESETS); - const char *ASPECT_RATIOS[] = { "stretch", "16:9", "4:3", }; @@ -37,9 +34,8 @@ struct button_map BUTTONS[] = { const int NUM_BUTTONS = ARRAY_SIZE(BUTTONS); /* host */ +DEFINE_OPTION_STRING(sync, "av", "Sync control"); DEFINE_OPTION_INT(bios, 0, "Boot to bios"); -DEFINE_OPTION_INT(audio, 1, "Enable audio"); -DEFINE_PERSISTENT_OPTION_INT(latency, 50, "Preferred audio latency in ms"); DEFINE_PERSISTENT_OPTION_INT(fullscreen, 0, "Start window fullscreen"); DEFINE_PERSISTENT_OPTION_INT(key_a, 'l', "A button mapping"); DEFINE_PERSISTENT_OPTION_INT(key_b, 'p', "B button mapping"); @@ -70,3 +66,23 @@ DEFINE_OPTION_INT(perf, 0, "Create maps for compiled code for use with perf"); /* ui */ DEFINE_PERSISTENT_OPTION_STRING(gamedir, "", "Directories to scan for games"); + +int audio_sync_enabled() { + const char *ptr = OPTION_sync; + while (*ptr) { + if (*(ptr++) == 'a') { + return 1; + } + } + return 0; +} + +int video_sync_enabled() { + const char *ptr = OPTION_sync; + while (*ptr) { + if (*(ptr++) == 'v') { + return 1; + } + } + return 0; +} diff --git a/src/options.h b/src/options.h index 68d70488..d19eb5fc 100644 --- a/src/options.h +++ b/src/options.h @@ -8,9 +8,6 @@ struct button_map { int *dirty; }; -extern const int LATENCY_PRESETS[]; -extern const int NUM_LATENCY_PRESETS; - extern const char *ASPECT_RATIOS[]; extern const int NUM_ASPECT_RATIOS; @@ -18,9 +15,8 @@ extern struct button_map BUTTONS[]; extern const int NUM_BUTTONS; /* host */ +DECLARE_OPTION_STRING(sync); DECLARE_OPTION_INT(bios); -DECLARE_OPTION_INT(audio); -DECLARE_OPTION_INT(latency); DECLARE_OPTION_INT(fullscreen); DECLARE_OPTION_INT(key_a); DECLARE_OPTION_INT(key_b); @@ -52,4 +48,7 @@ DECLARE_OPTION_INT(perf); /* ui */ DECLARE_OPTION_STRING(gamedir); +int audio_sync_enabled(); +int video_sync_enabled(); + #endif diff --git a/src/ui.c b/src/ui.c index 4470278a..0fa29e27 100644 --- a/src/ui.c +++ b/src/ui.c @@ -958,23 +958,7 @@ static void ui_audio_build(struct ui *ui) { igSetCursorPos(pos); igBeginChild("audio", size, false, ImGuiWindowFlags_NavFlattened); - igPushStyle_Btn(); - - { - if (igOptionInt("Latency (ms)", OPTION_latency, btn_size)) { - int next = 0; - for (int i = 0; i < NUM_LATENCY_PRESETS; i++) { - if (LATENCY_PRESETS[i] > OPTION_latency) { - next = i; - break; - } - } - OPTION_latency = LATENCY_PRESETS[next]; - OPTION_latency_dirty = 1; - } - } - - igPopStyle_Btn(); + igText("Not available yet."); igEndChild(); }