Retroarch:启动入口、android输入驱动

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