retroarch/frontend/frontend.c returntype main_entry(signature()) { ... while (!main_entry_iterate(signature_expand(), args)); } int main_entry_iterate(signature(), args_type() args) { if (g_extern.system.shutdown) return main_entry_iterate_shutdown(args); else if (g_extern.lifecycle_state & (1ULL << MODE_CLEAR_INPUT)) return main_entry_iterate_clear_input(args); else if (g_extern.lifecycle_state & (1ULL << MODE_LOAD_GAME)) return main_entry_iterate_load_content(args); else if (g_extern.lifecycle_state & (1ULL << MODE_GAME)) return main_entry_iterate_content(args); #ifdef HAVE_MENU else if (g_extern.lifecycle_state & (1ULL << MODE_MENU_PREINIT)) main_entry_iterate_menu_preinit(args); else if (g_extern.lifecycle_state & (1ULL << MODE_MENU)) return main_entry_iterate_menu(args); #endif else return 1; return 0; } static int main_entry_iterate_content(args_type() args) { bool r; if (g_extern.is_paused && !g_extern.is_oneshot) r = rarch_main_idle_iterate(); else r = rarch_main_iterate(); if (r) { if (frontend_ctx && frontend_ctx->process_events) frontend_ctx->process_events(args); } else g_extern.lifecycle_state &= ~(1ULL << MODE_GAME); return 0; } retroarch/retroarch.c bool rarch_main_iterate(void) { ... for (i = 0; i < MAX_PLAYERS; i++) { input_push_analog_dpad(g_settings.input.binds[i], g_settings.input.analog_dpad_mode[i]); input_push_analog_dpad(g_settings.input.autoconf_binds[i], g_settings.input.analog_dpad_mode[i]); } update_frame_time(); pretro_run(); limit_frame_time(); for (i = 0; i < MAX_PLAYERS; i++) { input_pop_analog_dpad(g_settings.input.binds[i]); input_pop_analog_dpad(g_settings.input.autoconf_binds[i]); } ... } pretro_run是一个指向retro_run的函数指针, 见dynamic.c的load_symbols()的SYM(retro_run); retro_run()函数由每一个ROM模拟器实现,比如 libretro-fceumm/src/drivers/libretro.c void retro_run(void) { ... FCEUI_Emulate(&gfx, &sound, &ssize, 0); if (ssize) FCEUD_WriteSoundData(sound, ssize); ... video_cb(video_out, width, height, pitch); ... } libretro-fceumm/src/fceu.c void FCEUI_Emulate(uint8 **pXBuf, int32 **SoundBuf, int32 *SoundBufSize, int skip) { int r, ssize; FCEU_UpdateInput(); if (geniestage != 1) FCEU_ApplyPeriodicCheats(); r = FCEUPPU_Loop(skip); ssize = FlushEmulateSound(); timestampbase += timestamp; timestamp = 0; *pXBuf = skip ? 0 : XBuf; *SoundBuf = WaveFinal; *SoundBufSize = ssize; } FCEUPPU_Loop模拟了CPU指令执行, FCEU_UpdateInput更新输入 libretro-fceumm/src/drivers/libretro/libretro.c static void FCEUD_UpdateInput(void) { unsigned i; unsigned char pad[2]; pad[0] = 0; pad[1] = 0; poll_cb(); for ( i = 0; i < 8; i++) pad[0] |= input_cb(0, RETRO_DEVICE_JOYPAD, 0, bindmap[i].retro) ? bindmap[i].nes : 0; for ( i = 0; i < 8; i++) pad[1] |= input_cb(1, RETRO_DEVICE_JOYPAD, 0, bindmap[i].retro) ? bindmap[i].nes : 0; JSReturn[0] = pad[0] | (pad[1] << 8); } poll_cb对android来说,最终会调用retroarch/android/native/jni/input_android.c 中的android_input_poll(),通过poll方式更新用户输入, input_cb最终调用input_android.c中的android_input_state(),取出用户输入 可见poll行为由ROM游戏触发 这些函数指针初始化地方: retroarch/retroarch.c static void init_libretro_cbs_plain(void) { pretro_set_video_refresh(video_frame); pretro_set_audio_sample(audio_sample); pretro_set_audio_sample_batch(audio_sample_batch); pretro_set_input_state(input_state); pretro_set_input_poll(rarch_input_poll); } void rarch_input_poll(void) { input_poll_func(); #ifdef HAVE_OVERLAY if (driver.overlay) // Poll overlay state input_poll_overlay(); #endif #ifdef HAVE_COMMAND if (driver.command) rarch_cmd_poll(driver.command); #endif } 其中input_poll_func调用input_android.c中的中的android_input_poll() #define input_poll_func() driver.input->poll(driver.input_data) #define input_input_state_func(retro_keybinds, port, device, index, id) \ driver.input->input_state(driver.input_data, retro_keybinds, port, device, index, id) static int16_t input_state(unsigned port, unsigned device, unsigned index, unsigned id) { device &= RETRO_DEVICE_MASK; #ifdef HAVE_BSV_MOVIE if (g_extern.bsv.movie && g_extern.bsv.movie_playback) { int16_t ret; if (bsv_movie_get_input(g_extern.bsv.movie, &ret)) return ret; else g_extern.bsv.movie_end = true; } #endif static const struct retro_keybind *binds[MAX_PLAYERS] = { g_settings.input.binds[0], g_settings.input.binds[1], g_settings.input.binds[2], g_settings.input.binds[3], g_settings.input.binds[4], g_settings.input.binds[5], g_settings.input.binds[6], g_settings.input.binds[7], }; int16_t res = 0; if (id < RARCH_FIRST_META_KEY || device == RETRO_DEVICE_KEYBOARD) res = input_input_state_func(binds, port, device, index, id); #ifdef HAVE_OVERLAY if (device == RETRO_DEVICE_JOYPAD && port == 0) res |= driver.overlay_state.buttons & (UINT64_C(1) << id) ? 1 : 0; else if (device == RETRO_DEVICE_KEYBOARD && port == 0 && id < RETROK_LAST) res |= OVERLAY_GET_KEY(&driver.overlay_state, id) ? 1 : 0; else if (device == RETRO_DEVICE_ANALOG && port == 0) { unsigned base = (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) ? 2 : 0; base += (id == RETRO_DEVICE_ID_ANALOG_Y) ? 1 : 0; if (driver.overlay_state.analog[base]) res = driver.overlay_state.analog[base]; } #endif #ifndef RARCH_CONSOLE // Don't allow turbo for D-pad. if (device == RETRO_DEVICE_JOYPAD && (id < RETRO_DEVICE_ID_JOYPAD_UP || id > RETRO_DEVICE_ID_JOYPAD_RIGHT)) res = input_apply_turbo(port, id, res); #endif #ifdef HAVE_BSV_MOVIE if (g_extern.bsv.movie && !g_extern.bsv.movie_playback) bsv_movie_set_input(g_extern.bsv.movie, res); #endif return res; } 其中input_input_state_func调用input_android.c中的android_input_state() 可以看到对overlay(手机屏幕浮层显示的虚拟手柄)输入也是在这里处理的 static void android_input_poll(void *data) { .... while ((ident = ALooper_pollAll((input_key_pressed_func(RARCH_PAUSE_TOGGLE)) ? -1 : 0, NULL, NULL, NULL)) >= 0) { if (ident == LOOPER_ID_INPUT) { while (AInputQueue_getEvent(android_app->inputQueue, &event) >= 0) { ... predispatched = AInputQueue_preDispatchEvent(android_app->inputQueue,event); AInputQueue_finishEvent(android_app->inputQueue, event, handled); .... } static int16_t android_input_state(void *data, const struct retro_keybind **binds, unsigned port, unsigned device, unsigned index, unsigned id) { android_input_t *android = (android_input_t*)data; switch (device) { case RETRO_DEVICE_JOYPAD: return ((android->pad_state[port] & binds[port][id].joykey) && (port < android->pads_connected)); case RETRO_DEVICE_ANALOG: if (port >= android->pads_connected) return 0; switch ((index << 1) | id) { case (RETRO_DEVICE_INDEX_ANALOG_LEFT << 1) | RETRO_DEVICE_ID_ANALOG_X: return android->analog_state[port][0][0]; case (RETRO_DEVICE_INDEX_ANALOG_LEFT << 1) | RETRO_DEVICE_ID_ANALOG_Y: return android->analog_state[port][0][1]; case (RETRO_DEVICE_INDEX_ANALOG_RIGHT << 1) | RETRO_DEVICE_ID_ANALOG_X: return android->analog_state[port][1][0]; case (RETRO_DEVICE_INDEX_ANALOG_RIGHT << 1) | RETRO_DEVICE_ID_ANALOG_Y: return android->analog_state[port][1][1]; } .... } 回到retroarch/frontend/frontend.c的main_entry_iterate_content() 其中调用了frontend_ctx->process_events(args); retroarch/frontend/platform/platform_android.c const frontend_ctx_driver_t frontend_ctx_android = { get_environment_settings, /* get_environment_settings */ system_init, /* init */ system_deinit, /* deinit */ NULL, /* exitspawn */ NULL, /* process_args */ process_events, /* process_events */ NULL, /* exec */ system_shutdown, /* shutdown */ "android", }; static int process_events(void *data) { struct android_app* android_app = (struct android_app*)data; if (input_key_pressed_func(RARCH_PAUSE_TOGGLE)) android_run_events(android_app); return 0; } input_key_pressed_func会调用driver的input的key_pressed函数指针 static inline bool input_key_pressed_func(int key) { bool ret = false; if (!driver.block_hotkey) ret = ret || driver.input->key_pressed(driver.input_data, key); #ifdef HAVE_OVERLAY ret = ret || (driver.overlay_state.buttons & (1ULL << key)); #endif #ifdef HAVE_COMMAND if (driver.command) ret = ret || rarch_cmd_get(driver.command, key); #endif return ret; } 对于android来说,key_pressed指向android_input_key_pressed retroarch/android/native/jni/input_android.c const input_driver_t input_android = { android_input_init, android_input_poll, android_input_state, android_input_key_pressed, android_input_free_input, android_input_set_keybinds, android_input_set_sensor_state, android_input_get_sensor_input, android_input_get_capabilities, android_input_devices_size, "android_input", }; static bool android_input_key_pressed(void *data, int key) { return ((g_extern.lifecycle_state | driver.overlay_state.buttons) & (1ULL << key)); } android_run_events() retroarch/frontend/platform/platform_android.c static bool android_run_events (void *data) { int id = ALooper_pollOnce(-1, NULL, NULL, NULL); if (id == LOOPER_ID_MAIN) engine_handle_cmd(driver.input_data); // Check if we are exiting. if (g_extern.lifecycle_state & (1ULL << RARCH_QUIT_KEY)) return false; return true; } void engine_handle_cmd(void *data) { struct android_app *android_app = (struct android_app*)g_android; int8_t cmd; if (read(android_app->msgread, &cmd, sizeof(cmd)) != sizeof(cmd)) cmd = -1; switch (cmd) { case APP_CMD_INPUT_CHANGED: slock_lock(android_app->mutex); if (android_app->inputQueue) AInputQueue_detachLooper(android_app->inputQueue); android_app->inputQueue = android_app->pendingInputQueue; if (android_app->inputQueue) { RARCH_LOG("Attaching input queue to looper"); AInputQueue_attachLooper(android_app->inputQueue, android_app->looper, LOOPER_ID_INPUT, NULL, NULL); } scond_broadcast(android_app->cond); slock_unlock(android_app->mutex); break; case APP_CMD_INIT_WINDOW: ... break; case APP_CMD_RESUME: ... break; case APP_CMD_START: ... break; case APP_CMD_DESTROY: g_extern.lifecycle_state |= (1ULL << RARCH_QUIT_KEY); break; } 可以看到这里的处理不涉及到游戏过程的用户输入的处理,仅仅处理 activity周期中的cmd事件 其他: retroarch/frontend/platform/platform_android.c 中初始化android相关函数: static void system_init(void *data) { JNIEnv *env; jclass class = NULL; jobject obj = NULL; struct android_app* android_app = (struct android_app*)data; ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, NULL); android_app->looper = looper; slock_lock(android_app->mutex); android_app->running = 1; scond_broadcast(android_app->cond); slock_unlock(android_app->mutex); memset(&g_android, 0, sizeof(g_android)); g_android = android_app; RARCH_LOG("Native Activity started.\n"); rarch_main_clear_state(); rarch_init_msg_queue(); while (!android_app->window) { if (!android_run_events(android_app)) { system_deinit(android_app); system_shutdown(android_app); } } env = jni_thread_getenv(); if (!env) return; GET_OBJECT_CLASS(env, class, android_app->activity->clazz); GET_METHOD_ID(env, android_app->getIntent, class, "getIntent", "()Landroid/content/Intent;"); CALL_OBJ_METHOD(env, obj, android_app->activity->clazz, android_app->getIntent); GET_OBJECT_CLASS(env, class, obj); GET_METHOD_ID(env, android_app->getStringExtra, class, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;"); }
附上两个结构体的定义:
typedef struct android_input { jmethodID onBackPressed; unsigned pads_connected; int state_device_ids[MAX_PADS]; uint64_t pad_state[MAX_PADS]; uint64_t keycode_lut[LAST_KEYCODE]; int16_t analog_state[MAX_PADS][2][2]; sensor_t accelerometer_state; unsigned dpad_emulation[MAX_PLAYERS]; struct input_pointer pointer[MAX_TOUCH]; unsigned pointer_count; ASensorManager* sensorManager; ASensorEventQueue* sensorEventQueue; } android_input_t; struct retro_keybind { bool valid; unsigned id; const char *desc; enum retro_key key; // PC only uses lower 16-bits. // Full 64-bit can be used for port-specific purposes, like simplifying multiple binds, etc. uint64_t joykey; // Default key binding value - for resetting bind to default uint64_t def_joykey; uint32_t joyaxis; uint32_t def_joyaxis; uint32_t orig_joyaxis; // Used by input_{push,pop}_analog_dpad(). };