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