added host debug menu

added audio/video sync option
enabled video sync by default
This commit is contained in:
Anthony Pesch 2017-11-20 18:41:49 -05:00
parent b3e1b38047
commit a080c5d71f
6 changed files with 233 additions and 183 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}