void fs_emu_video_after_update() { fs_emu_video_buffer_unlock(); int64_t t = fs_emu_monotonic_time(); if (fs_emu_cursor_is_visible_to() > 0) { if (fs_emu_cursor_is_visible_to() < fs_emu_monotonic_time()) { //fs_log("%lld\n", fs_emu_monotonic_time()); fs_emu_show_cursor(0); } } update_leds(t); update_video_stats_system_video(); if (g_fs_emu_benchmark_start_time) { static int64_t last_report = 0; if (t - last_report > 5000000) { double ttime = ((t - g_fs_emu_benchmark_start_time) / 1000000.0); double sys_fps = g_fs_emu_total_sys_frames / ttime; double emu_fps = g_fs_emu_total_emu_frames / ttime; //fs_log("average fps sys: %0.1f emu: %0.1f\n", sys_fps, emu_fps); printf("average fps sys: %0.1f emu: %0.1f\n", sys_fps, emu_fps); last_report = t; } } }
static void update_video_stats_system_video() { if (g_fs_emu_benchmark_start_time > 0) { g_fs_emu_total_sys_frames++; } static int64_t frame_time_first = 0; static int64_t frame_time_last = 0; int t = fs_emu_monotonic_time(); if (frame_time_first == 0) { frame_time_first = t; } int time_ms = (t - frame_time_first) / 1000; int dt = (int) (time_ms - frame_time_last); // more than 5 seconds => do not record entry (abnormality) fs_emu_stat_queue_add_entry(&g_fs_emu_sys_frame_times, dt, 5 * 1000); frame_time_last = time_ms; double refresh_rate = fs_ml_get_refresh_rate(); // check if we have missed a vblank internal, but only after 2 seconds // after first render if (time_ms > 2000 && refresh_rate > 0) { if (dt > 1.5 * 1000.0 / refresh_rate) { g_fs_emu_lost_vblanks++; } } }
static void on_quit() { fs_log("libfsemu:on_quit\n"); g_fs_emu_quit_time = fs_emu_monotonic_time(); if (g_quit_function) { g_quit_function(); } // FIXME: detached? fs_thread_create("force-quit", force_quit_thread, NULL); }
void fs_emu_update_video_stats_1() { int t = fs_emu_monotonic_time(); if (g_frame_time_first == 0) { return; } int time_ms = (t - g_frame_time_first) / 1000; int dt = (int) (time_ms - g_frame_time_last); // more than 5 seconds => do not record entry (abnormality) fs_emu_stat_queue_add_entry(&g_fs_emu_emu2_frame_times, dt, 5 * 1000); }
int fs_emu_run(fs_emu_main_function function) { fs_emu_log("fs_emu_run, main_function at %p\n", function); // FIXME: should wait until we are certain that the video thread is // running (i.e. wait for a status / flag) #ifdef WITH_NETPLAY // FIXME: MOVE if (fs_emu_netplay_enabled()) { fs_log("netplay is enabled\n"); fs_emu_netplay_start(); } #endif g_emulation_thread = fs_thread_create( "emulation", emulation_thread_entry, function); if (g_emulation_thread == NULL) { fs_emu_log("error starting video thread\n"); // FIXME: ERROR MESSAGE HERE // FIXME: FATAL } #ifdef FS_EMU_DRIVERS int result = fs_emu_main_loop(); #else int result = fs_ml_main_loop(); #endif fs_emu_log("fs_emu_run: main loop is done\n"); if (g_fs_emu_benchmark_start_time) { int64_t t2 = fs_emu_monotonic_time(); double ttime = ((t2 - g_fs_emu_benchmark_start_time) / 1000000.0); double sys_fps = g_fs_emu_total_sys_frames / ttime; double emu_fps = g_fs_emu_total_emu_frames / ttime; fs_log("average fps sys: %0.1f emu: %0.1f\n", sys_fps, emu_fps); } fs_emu_log("fs_emu_run: waiting for emulation thread to stop\n"); while (g_fs_emu_emulation_thread_running) { fs_emu_msleep(1); } fs_emu_log("fs_emu_run: emulation thread stopped\n"); #ifdef USE_SDL_AUDIO fs_emu_log("fs_emu_run: calling SDL_CloseAudio\n"); SDL_CloseAudio(); #endif fs_emu_audio_shutdown(); fs_emu_log("fs_emu_run: returning\n"); return result; }
static void on_quit() { g_fs_emu_quit_time = fs_emu_monotonic_time(); /* if (g_post_quit_function) { fs_log("libfsemu on_quit: executing quit function\n"); g_post_quit_function(); } else { fs_log("libfsemu on_quit: no quit function\n"); } */ // FIXME: detached? fs_thread_create("force-quit", force_quit_thread, NULL); }
static int wait_for_frame_no_netplay(void) { #if 0 while (1) { fs_ml_usleep(100 * 1000); } #endif if (g_fs_emu_benchmarking) { return 1; } #ifdef FSE_DRIVERS if (g_fs_emu_benchmark_mode) { return 1; } #endif if (!g_fs_emu_throttling) { return 1; } //fs_log("wait_for_frame_no_netplay\n"); static int64_t last_time = 0; static int64_t frame_time = 0; if (last_time == 0) { last_time = fs_emu_monotonic_time(); } static double last_frame_rate = 0.0; double frame_rate = fs_emu_get_video_frame_rate(); if (frame_rate != last_frame_rate) { frame_time = ((int64_t) 1000000) / frame_rate; fs_log("wait_for_frame_no_netplay: new frame rate %0.2f (time: %d)\n", frame_rate, (int) frame_time); last_frame_rate = frame_rate; } int64_t wait_until = last_time + frame_time; //int64_t sleep_until = wait_until; int64_t sleep_until = wait_until - 100; int64_t t = fs_emu_monotonic_time(); //fs_log("%lld %lld\n", sleep_until, t); while (t < sleep_until) { int64_t sleep_time = sleep_until - t; //fs_log("%lld %lld %lld\n", sleep_until, t, sleep_time); fs_ml_usleep(sleep_time); t = fs_emu_monotonic_time(); } while (t < wait_until) { t = fs_emu_monotonic_time(); } last_time = last_time + frame_time; if (fs_emu_monotonic_time() > last_time + frame_time) { // time has elapsed too far, probably due to pause function having // been used last_time = fs_emu_monotonic_time(); } return 1; }
static void full_sleep_until_vsync() { // FIXME: use this instead of sleep_until_vsync int sleep_time = 0; int time_left = 3000; int64_t t = fs_emu_monotonic_time(); if (g_fs_ml_target_frame_time > 0) { sleep_time = g_sleep_until_vsync_last_time + g_fs_ml_target_frame_time - t - time_left; } if (sleep_time > g_fs_ml_target_frame_time - time_left) { sleep_time = 0; } if (sleep_time > 0) { fs_ml_usleep(sleep_time); } }
static void sleep_until_vsync() { int sleep_time = 5000; int64_t t = fs_emu_monotonic_time(); //int64_t sleep_until = 0; if (g_fs_ml_target_frame_time > 0) { // calculate sleep time based on frame rate // (allowing 4 ms busy-waiting by OpenGL driver, if necessary) //sleep_time = g_fs_ml_target_frame_time - 4000; sleep_time = g_sleep_until_vsync_last_time + g_fs_ml_target_frame_time - t - 5000; } if (sleep_time > g_fs_ml_target_frame_time - 4000) { sleep_time = 0; } if (sleep_time > 0) { //printf("sleep %d\n", sleep_time); fs_ml_usleep(sleep_time); } }
/** * This function is called at the end of the frame rendering function */ static void handle_quit_sequence() { int fade_time = 750 * 1000; int64_t dt = fs_emu_monotonic_time() - g_fs_emu_quit_time; if (dt > fade_time && g_fs_emu_emulation_thread_stopped) { fs_emu_log("calling fs_ml_stop because emu thread is done\n"); fs_ml_stop(); } else if (dt > 5 * 1000 * 1000) { // 5 seconds has passed after shutdown was requested fs_emu_log("calling fs_ml_stop because emu does not stop\n"); // FIXME: FORCE STOP fs_ml_stop(); fs_emu_log("force-closing the emulator\n"); exit(1); } // fade out over 750ms float fade = (1.0 * dt) / fade_time; if (fade > 1.0) { fade = 1.0; } // draw fading effect fs_gl_viewport(0, 0, fs_ml_video_width(), fs_ml_video_height()); fs_gl_ortho_hd(); fs_gl_blending(1); fs_gl_texturing(0); fs_gl_color4f(0.0, 0.0, 0.0, fade); GLfloat vert[] = { 0, 0, 1920, 0, 1920, 1080, 0, 1080 }; glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_FLOAT, 0, vert); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisableClientState(GL_VERTEX_ARRAY); CHECK_GL_ERROR(); }
static void render_iteration_vsync() { if (g_fs_ml_video_sync_low_latency) { int current_frame_at_start = g_available_frame; //int64_t t1 = fs_ml_monotonic_time(); int sleep_time = 0; int time_left = g_estimated_upload_render_duration; int64_t t = fs_emu_monotonic_time(); if (g_fs_ml_target_frame_time > 0) { sleep_time = g_estimated_next_vblank_time - t - time_left; } if (sleep_time > g_fs_ml_target_frame_time - time_left) { sleep_time = 0; } if (sleep_time > 0) { fs_ml_usleep(sleep_time); } if (g_available_frame > current_frame_at_start) { //printf("low latency %d\n", g_available_frame); } else { //printf("...\n"); } } update_frame(); CHECK_GL_ERROR_MSG("update_frame"); render_frame(); CHECK_GL_ERROR_MSG("render_frame"); //opengl_fence(FENCE_SET); //glFlush(); //opengl_fence(FENCE_WAIT); //int64_t upload_render_time = fs_ml_monotonic_time() - t1; //printf("urt %lld\n", upload_render_time); opengl_swap_synchronous(); g_measured_vblank_time = fs_ml_monotonic_time(); g_vblank_count++; fs_mutex_lock(g_vblank_mutex); g_measured_vblank_times[g_vblank_index] = g_measured_vblank_time; g_vblank_index = (g_vblank_index + 1) % VBLANK_COUNT; fs_mutex_unlock(g_vblank_mutex); // FIXME: adjust g_measured_vblank_time based on historical data (smooth out // irregularities) and save the result in g_adjusted_vblank_time g_adjusted_vblank_time = g_measured_vblank_time; g_sleep_until_vsync_last_time = g_adjusted_vblank_time; g_estimated_next_vblank_time = g_adjusted_vblank_time + \ g_fs_ml_target_frame_time; // g_start_new_frame_cond is used to signal that a new frame can be // generated when the emulation is running in sync - this is not used // when only display flipping is synced to vblank fs_mutex_lock(g_start_new_frame_mutex); g_start_new_frame = 1; fs_condition_signal(g_start_new_frame_cond); fs_mutex_unlock(g_start_new_frame_mutex); }
int fs_ml_video_create_window(const char *title) { fs_log("fs_ml_video_create_window\n"); g_window_title = g_strdup(title); g_fs_ml_keyboard_input_grab = fs_config_get_boolean( "keyboard_input_grab"); if (g_fs_ml_automatic_input_grab == FS_CONFIG_NONE) { g_fs_ml_keyboard_input_grab = 1; } fs_log("keyboard input grab: %d\n", g_fs_ml_keyboard_input_grab); static int initialized = 0; SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, g_fs_ml_keyboard_input_grab ? "1" : "0"); SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); #ifdef WINDOWS SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1"); #endif SDL_Init(SDL_INIT_VIDEO); SDL_version cversion, lversion; SDL_VERSION(&cversion); SDL_GetVersion(&lversion); fs_log("[SDL] Version %d.%d.%d (Compiled against %d.%d.%d)\n", lversion.major, lversion.minor, lversion.patch, cversion.major, cversion.minor, cversion.patch); if (!initialized) { int display_index = 0; SDL_DisplayMode mode; int error = SDL_GetCurrentDisplayMode(display_index, &mode); if (error) { fs_log("SDL_GetCurrentDisplayMode failed\n"); SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Display Error", "SDL_GetCurrentDisplayMode failed.", NULL); exit(1); } fs_emu_monitor_init(); const char *mon = fs_config_get_const_string("monitor"); int mon_flag = -1; if (mon == NULL) { mon = "middle-left"; } if (strcmp(mon, "left") == 0) { mon_flag = FS_EMU_MONITOR_FLAG_LEFT; } else if (strcmp(mon, "middle-left") == 0) { mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_LEFT; } else if (strcmp(mon, "middle-right") == 0) { mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_RIGHT; } else if (strcmp(mon, "right") == 0) { mon_flag = FS_EMU_MONITOR_FLAG_RIGHT; } else { mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_LEFT; } FSEmuMonitor monitor; fs_emu_monitor_get_by_flag(mon_flag, &monitor); fs_log("Monitor \"%s\" (flag %d) => index %d\n", mon, mon_flag, monitor.index); g_display = monitor.index; g_fullscreen_width = fs_config_get_int("fullscreen_width"); if (g_fullscreen_width == FS_CONFIG_NONE) { g_fullscreen_width = mode.w; } g_fullscreen_height = fs_config_get_int("fullscreen_height"); if (g_fullscreen_height == FS_CONFIG_NONE) { g_fullscreen_height = mode.h; } if (g_fs_emu_video_fullscreen_mode_string == NULL) { g_fs_emu_video_fullscreen_mode = -1; } else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string, "window") == 0) { g_fs_emu_video_fullscreen_mode = FULLSCREEN_WINDOW; } else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string, "fullscreen") == 0) { g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN; } else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string, "desktop") == 0) { g_fs_emu_video_fullscreen_mode = FULLSCREEN_DESKTOP; } if (g_fs_emu_video_fullscreen_mode == -1) { #ifdef MACOSX g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN; #else g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN; #endif fs_log("[SDL] Defaulting to fullscreen_mode = desktop for SDL 2\n"); g_fs_emu_video_fullscreen_mode = FULLSCREEN_DESKTOP; } initialized = 1; } if (g_fs_ml_video_sync) { g_fs_ml_vblank_sync = 1; } SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); if (g_fsaa) { fs_log("setting FSAA samples to %d\n", g_fsaa); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, g_fsaa); } g_window_width = fs_config_get_int("window_width"); if (g_window_width == FS_CONFIG_NONE) { g_window_width = 1920 / 2; } g_window_height = fs_config_get_int("window_height"); if (g_window_height == FS_CONFIG_NONE) { g_window_height = 1080/ 2; } g_window_x = fs_config_get_int("window_x"); if (g_window_x == FS_CONFIG_NONE) { g_window_x = SDL_WINDOWPOS_CENTERED; } g_window_y = fs_config_get_int("window_y"); if (g_window_y == FS_CONFIG_NONE) { g_window_y = SDL_WINDOWPOS_CENTERED; } g_window_resizable = fs_config_get_boolean("window_resizable"); if (g_window_resizable == FS_CONFIG_NONE) { g_window_resizable = 1; } g_fs_ml_automatic_input_grab = fs_config_get_boolean( "automatic_input_grab"); if (g_fs_ml_automatic_input_grab == FS_CONFIG_NONE) { if (fs_ml_mouse_integration()) { g_fs_ml_automatic_input_grab = 0; } else { g_fs_ml_automatic_input_grab = 1; } } fs_log("automatic input grab: %d\n", g_fs_ml_automatic_input_grab); g_initial_input_grab = g_fs_ml_automatic_input_grab; if (fs_config_get_boolean("initial_input_grab") == 1) { g_initial_input_grab = 1; } else if (fs_config_get_boolean("initial_input_grab") == 0 || // deprecated names: fs_config_get_boolean("input_grab") == 0 || fs_config_get_boolean("grab_input") == 0) { g_initial_input_grab = 0; } set_video_mode(); if (g_fs_ml_vblank_sync) { fs_emu_log("*** Setting swap interval to 1 ***\n"); if (SDL_GL_SetSwapInterval(1) != 0) { fs_emu_warning("SDL_GL_SetSwapInterval(1) failed"); } } else { fs_emu_log("*** Setting swap interval to 0 ***\n"); SDL_GL_SetSwapInterval(0); } fs_log("initial input grab: %d\n", g_initial_input_grab); if (g_initial_input_grab && !g_has_input_grab) { fs_ml_set_input_grab(true); } fs_ml_show_cursor(0, 1); /* This looks a bit peculiar, but it helps to show the window in * fullscreen mode as soon as possible to reduce flickering, at least under GNOME 3. */ glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); SDL_GL_SwapWindow(g_fs_ml_window); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); SDL_GL_SwapWindow(g_fs_ml_window); int64_t start_time = fs_emu_monotonic_time(); SDL_Event event; while (fs_emu_monotonic_time() - start_time < 100 * 1000) { SDL_WaitEventTimeout(&event, 10); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); SDL_GL_SwapWindow(g_fs_ml_window); } // this function must be called from the video thread fs_log("init_opengl\n"); fse_init_video_opengl(); SDL_StartTextInput(); #ifdef WINDOWS if (!fs_config_false(OPTION_RAW_INPUT)) { fs_ml_init_raw_input(); } #endif fs_log("create windows is done\n"); return 1; }