VitaでZAVASやりたくて(11)

前回までのあらすじ

Vita版Retroarchでソフトウェアキーボードを扱うべくRetroarchを改造するぞと息巻いてだいたいこのへんをいじれば良さそうだなとあたりをつけたところで長すぎて一旦CMですしたのが前回でした。
今回はいよいよVita版をいじっちゃうぞの巻。

Retroarch改造計画~後編

小道具:printfデバッグもどき

いじっていく前に、あるとちょっと便利な道具を紹介しておきます。
Vita版を開発してるとデバッガみたいなもんがないので(あるのかな?)せめて画面に何か出しながら確認、いわゆるprintfデバッグしたくなると思いますが、これでなんちゃってprintfデバッグできます。

#include <queues/message_queue.h>

void runloop_msg_queue_push(const char *msg,
      unsigned prio, unsigned duration,
      bool flush,
      char *title,
      enum message_queue_icon icon,
      enum message_queue_category category);

Retroarch起動時とかで何やらポップアップが出ることがありますけど(アセットがない!とか)、あれを出すやつです。
こんな感じでブロックごと突っ込んでしまえば好きなとこで出せるぞ。
(※ただしポップアップ表示なので出しすぎには注意しましょう。出しすぎるとひどくうざいよ)

{
	char msg[256];
	sprintf(msg, "%x:%x", sceKernelGetModel(), sceKernelGetModelForCDialog());	// どちらも同じ結果が取れる(SCE_KERNEL_MODEL_VITA = 0x10000)っぽいけど何が違うのだろうか…
	runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}

Android版とVita版を見比べてみる

まずは入力管理用の構造体を見てみます。
前回みつけたandroid_input_tとそれに対応するpsp_input_tはこんな感じ。

// Android版 from input/drivers/android_input.c
struct input_pointer
{
   int16_t x, y;
   int16_t full_x, full_y;
};
...
typedef struct android_input
{
   int64_t quick_tap_time;
   state_device_t pad_states[MAX_USERS];        /* int alignment */
   int mouse_x_delta, mouse_y_delta;
   int mouse_l, mouse_r, mouse_m, mouse_wu, mouse_wd;
   unsigned pads_connected;
   unsigned pointer_count;
   sensor_t accelerometer_state;                /* float alignment */
   sensor_t gyroscope_state;                    /* float alignment */
   float mouse_x_prev, mouse_y_prev;
   struct input_pointer pointer[MAX_TOUCH];     /* int16_t alignment */
   char device_model[256];
} android_input_t;


// Vita版 from input/drivers/psp_input.c
typedef struct psp_input
{
   int keyboard_hid_handle;
   int mouse_hid_handle;
   int32_t mouse_x;
   int32_t mouse_y;
   int32_t mouse_x_delta;
   int32_t mouse_y_delta;
   uint8_t prev_keys[6];
   bool keyboard_state[VITA_MAX_SCANCODE + 1];
   bool mouse_button_left;
   bool mouse_button_right;
   bool mouse_button_middle;
   bool sensors_enabled;
} psp_input_t;

これは単純にstruct input_pointerを追加すれば良さげです。
問題はMAX_TOUCHですが、VitaSDKのドキュメントによれば「SCE_TOUCH_MAX_REPORT」がこれに対応するのかな?

◆ SCE_TOUCH_MAX_REPORT
#define SCE_TOUCH_MAX_REPORT 8
FIXME 6 on front | 4 on back.

from VitaSDK

なにやら補足説明が気になるところではありますけど最大値という意味では大丈夫だろうということでこれを採用。

次はタッチ座標を取得してるあたり。
Android版がandroid_input_poll_event_type_motion、Vita版がvita_input_pollでした。
Vita版には当然ながら単にタッチ関連処理がない状態なので、Android版のコードをどう変えて持っていけば良いか見てみます。
Android版ではタッチ座標を取得するのにこういう処理をしていました。

      int      pointer_max     = MIN(
            AMotionEvent_getPointerCount(event), MAX_TOUCH);
      ...

      for (motion_ptr = 0; motion_ptr < pointer_max; motion_ptr++)
      {
         struct video_viewport vp;
         float x = AMotionEvent_getX(event, motion_ptr);
         float y = AMotionEvent_getY(event, motion_ptr);

         vp.x                        = 0;
         vp.y                        = 0;
         vp.width                    = 0;
         vp.height                   = 0;
         vp.full_width               = 0;
         vp.full_height              = 0;

         video_driver_translate_coord_viewport_wrap(
               &vp,
               x, y,
               &android->pointer[motion_ptr].x,
               &android->pointer[motion_ptr].y,
               &android->pointer[motion_ptr].full_x,
               &android->pointer[motion_ptr].full_y);

         android->pointer_count = MAX(
               android->pointer_count,
               motion_ptr + 1);
      }
   }

見た感じプラットフォーム依存の部分は「AMotionEvent_getPointerCount」「AMotionEvent_getX」「AMotionEvent_getY」くらいの様子。で、これがタッチ座標を取得するのに使う関数らしい。(AndroidのNDK版の関数)
気になるのが型がfloatになってるとこですが、使用例をググってみると、返ってくる値自体は普通にスクリーン上の座標になっているようです。分解能の関係でタッチ座標の方が細かくとれるのかな。
ここをVitaでタッチ座標を取得する関数に置き換えれば良いはず。
もう忘れてると思いますけどVitaでタッチ座標を取得する処理はinput/drivers_joypad/psp_joypad.cで予習済みでした。

      if (sceKernelGetModelForCDialog() == SCE_KERNEL_MODEL_VITA
         && input_backtouch_enable)
      {
         unsigned i;
         SceTouchData touch_surface = {0};
         sceTouchPeek(input_backtouch_toggle
               ? SCE_TOUCH_PORT_FRONT 
               : SCE_TOUCH_PORT_BACK,
               &touch_surface, 1);

         for (i = 0; i < touch_surface.reportNum; i++)
         {
            int x = LERP(touch_surface.report[i].x,
                  TOUCH_MAX_WIDTH, SCREEN_WIDTH);
            int y = LERP(touch_surface.report[i].y,
                  TOUCH_MAX_HEIGHT, SCREEN_HEIGHT);
            if (NW_AREA(x, y))
               state_tmp.buttons |= PSP_CTRL_L2;
            if (NE_AREA(x, y))
               state_tmp.buttons |= PSP_CTRL_R2;
            if (SW_AREA(x, y))
               state_tmp.buttons |= PSP_CTRL_L3;
            if (SE_AREA(x, y))
               state_tmp.buttons |= PSP_CTRL_R3;
         }
      }

Vitaでは「sceTouchPeek」で「AMotionEventなんとか」相当を全部やってくれるみたいですね。
タッチ座標を「sceTouchPeek」を取得した上で、あとはAndroid版をベースにして単にreportNumでループを回してあげれば良さそう。
ちょっと注意が必要なのがVitaではタッチの解像度が画面の解像度の2倍ある点です。xとかyとかの変数は画面上の座標に合わせる必要があるので半分にして使う必要があります。

最後はタッチ座標を返すあたり。
Android版がandroid_input_stateでVita版がvita_input_stateになります。
まずはAndroid版。(ソースコードはcase文の中身を豪快に端折ってます)
RETRO_DEVICE_POINTER、RARCH_DEVICE_POINTER_SCREENの場合にタッチ座標を返すようになっていました。

   switch (device)
   {
      case RETRO_DEVICE_JOYPAD:
         ...
      case RETRO_DEVICE_ANALOG:
         ...
      case RETRO_DEVICE_KEYBOARD:
         ...
      case RETRO_DEVICE_MOUSE:
         ...
      case RETRO_DEVICE_LIGHTGUN:
         ...
      case RETRO_DEVICE_POINTER:
      case RARCH_DEVICE_POINTER_SCREEN:
         switch (id)
         {
            case RETRO_DEVICE_ID_POINTER_X:
               if (device == RARCH_DEVICE_POINTER_SCREEN)
                  return android->pointer[idx].full_x;
               return android->pointer[idx].x;
            case RETRO_DEVICE_ID_POINTER_Y:
               if (device == RARCH_DEVICE_POINTER_SCREEN)
                  return android->pointer[idx].full_y;
               return android->pointer[idx].y;
            case RETRO_DEVICE_ID_POINTER_PRESSED:
               if (device == RARCH_DEVICE_POINTER_SCREEN)
                  return (idx < android->pointer_count) &&
                     (android->pointer[idx].full_x != -0x8000) &&
                     (android->pointer[idx].full_y != -0x8000);
               return (idx < android->pointer_count) &&
                  (android->pointer[idx].x != -0x8000) &&
                  (android->pointer[idx].y != -0x8000);
            case RETRO_DEVICE_ID_POINTER_COUNT:
               return android->pointer_count;
            case RARCH_DEVICE_ID_POINTER_BACK:
            {
               const struct retro_keybind *keyptr = 
                  &input_autoconf_binds[0][RARCH_MENU_TOGGLE];
               if (keyptr->joykey == 0)
                  return ANDROID_KEYBOARD_INPUT_PRESSED(AKEYCODE_BACK);
            }
         }
         break;
   }

最後のRARCH_DEVICE_ID_POINTER_BACKが謎めいてるのを忘れれば(これは後ほど紹介するソースコード内で説明を入れるに留めておきます)、基本的にはタッチ座標をもとに何かしら返すだけのようです。
続いてVita版。

   switch (device)
   {
      case RETRO_DEVICE_JOYPAD:
         ...
      case RETRO_DEVICE_ANALOG:
         ...
      case RETRO_DEVICE_KEYBOARD:
         ...
      case RETRO_DEVICE_MOUSE:
         ...
      case RARCH_DEVICE_MOUSE_SCREEN:
         ...
   }

対応してるリクエストの種類が微妙に違いますけど、タッチに相当するRETRO_DEVICE_POINTER、RARCH_DEVICE_POINTER_SCREENがまるまる抜けてるのでそれを単純に追加してあげれば良さそう。

いざRetroarch改造

改造初回(まだ失敗するよ)

改造計画で調査した内容をいよいよVita版に適用してみます。
こんな感じでinput/drivers/psp_input.cを変更するッ!
(例によってMOD BEGINからMOD ENDまでが変更箇所)

構造体定義付近。

/* ---------- MOD BEGIN ---------- */
// ****************************************************************************************************
// #ifdef部分を見るとSN_TARGET_PSP2とVITAで書き分ける必要があるような気もするのだが、正直なにがどう違うのかよく分らんので気にせずごちゃまぜにしてます。
// ****************************************************************************************************

#include <psp2/kernel/sysmem.h>
#include <psp2/touch.h>
#include "../../config.def.h"

// ------------------------------------------------------------
// タッチ座標記録用に構造体を拡張する。
// android_input.cの処理を参考にしてVita用に書き換え。
// ------------------------------------------------------------

struct input_pointer
{
   int16_t x, y;
   int16_t full_x, full_y;
};
/* ---------- MOD END ---------- */

typedef struct psp_input
{
   int keyboard_hid_handle;
   int mouse_hid_handle;
   int32_t mouse_x;
   int32_t mouse_y;
   int32_t mouse_x_delta;
   int32_t mouse_y_delta;
   uint8_t prev_keys[6];
   bool keyboard_state[VITA_MAX_SCANCODE + 1];
   bool mouse_button_left;
   bool mouse_button_right;
   bool mouse_button_middle;
   bool sensors_enabled;

/* ---------- MOD BEGIN ---------- */
   unsigned pointer_count;
   struct input_pointer pointer[SCE_TOUCH_MAX_REPORT];
/* ---------- MOD END ---------- */
} psp_input_t;

タッチ座標取得付近。

static void vita_input_poll(void *data)
{
   psp_input_t *psp     = (psp_input_t*)data;
   unsigned int i       = 0;
   int key_sym          = 0;
   unsigned key_code    = 0;
   uint8_t mod_code     = 0;
   uint16_t mod         = 0;
   uint8_t modifiers[2] = { 0, 0 };
   bool key_held        = false;
   int mouse_velocity_x = 0;
   int mouse_velocity_y = 0;
   SceHidKeyboardReport k_reports[SCE_HID_MAX_REPORT];
   SceHidMouseReport m_reports[SCE_HID_MAX_REPORT];

/* ---------- MOD BEGIN ---------- */
   // ------------------------------------------------------------
   // タッチ機能用の処理を追加。
   // android_input.cの処理を参考にしてVita用に書き換え。
   // ------------------------------------------------------------

   // VitaかVitaTVかを切り分ける流儀があるようなのでとりあえず真似しているのだがもしかしたら余計かもしれない。
   if (sceKernelGetModelForCDialog() == SCE_KERNEL_MODEL_VITA) {
      // タッチ座標を取得&記録していく。
      // タッチ座標の情報はタッチパネルを想定しているはず(画面上の何かをタッチする)なのでフロントパネル限定で問題ないはず。
      SceTouchData touch_surface = {0};
      sceTouchPeek(SCE_TOUCH_PORT_FRONT, &touch_surface, 1);

      // 取得できたタッチ情報を総なめ。マルチタッチなので複数取得できるよ。
      // Android版ではループ変数がmotion_ptrになっているが分かりにくいだけなので業界標準の「i」に変更している。
      // (intで問題ないだろうけどpsp_joypad.cにならってunsignedにしておいた)
      for (unsigned i = 0; i < touch_surface.reportNum; i++)
      {
         // Vitaではタッチパネルの解像度が画面の解像度の2倍である点に注意すること。(タッチ1920x1088、画面960x544)
         // Retroarchの関数はタッチ解像度が画面解像度と同じであることを前提にしているようなので(常識的にはそうだよね)、あらかじめ半分にして画面側に合わせておく必要がある。
         // floatにする必要は特にないのだがなんとなくAndroid版に合わせた。
         struct video_viewport vp;
         float x = touch_surface.report[i].x / 2.0;
         float y = touch_surface.report[i].y / 2.0;

         vp.x                        = 0;
         vp.y                        = 0;
         vp.width                    = 0;
         vp.height                   = 0;
         vp.full_width               = 0;
         vp.full_height              = 0;

         // Vita座標系からタッチ用座標系へ変換して記録する。
         // タッチ用座標系は画面の真ん中が原点(0,0)で、x,y座標とも画面の端から端までを-32727から32767までで表現した座標系。
         // 画面全体ベースでの座標がfull_x,full_yに、エミュレータ画面部分ベースでの座標がx,yになる。
         // タッチ座標を要求する側でどちらが必要かを指定するようだ。
         // メニューとかのタッチとゲーム画面内のタッチとで使い分けるみたいな感じなのだろう。
         video_driver_translate_coord_viewport_wrap(
               &vp,
               x, y,
               &psp->pointer[i].x,
               &psp->pointer[i].y,
               &psp->pointer[i].full_x,
               &psp->pointer[i].full_y);
      }

      // Android版はなにやらごちゃごちゃやってるけど取得できた総数をそのまま代入して問題ないはず。
      psp->pointer_count = touch_surface.reportNum;
   }
/* ---------- MOD END ---------- */

タッチ座標返す付近。

static int16_t vita_input_state(
      void *data,
      const input_device_driver_t *joypad,
      const input_device_driver_t *sec_joypad,
      rarch_joypad_info_t *joypad_info,
      const retro_keybind_set *binds,
      bool keyboard_mapping_blocked,
      unsigned port,
      unsigned device,
      unsigned idx,
      unsigned id)
{
   psp_input_t *psp           = (psp_input_t*)data;

   switch (device)
   {
      case RETRO_DEVICE_JOYPAD:
      case RETRO_DEVICE_ANALOG:
         break;
#ifdef VITA
      case RETRO_DEVICE_KEYBOARD:
         ...

/* ---------- MOD BEGIN ---------- */
      // ------------------------------------------------------------
      // 要求に応じてタッチ情報を返す。
      // android_input.cの処理を参考にしてVita用に書き換え。
      // ------------------------------------------------------------
      case RETRO_DEVICE_POINTER:
      case RARCH_DEVICE_POINTER_SCREEN:
         switch (id)
         {
            case RETRO_DEVICE_ID_POINTER_X:
               if (device == RARCH_DEVICE_POINTER_SCREEN)
                  return psp->pointer[idx].full_x;
               return psp->pointer[idx].x;
            case RETRO_DEVICE_ID_POINTER_Y:
               if (device == RARCH_DEVICE_POINTER_SCREEN)
                  return psp->pointer[idx].full_y;
               return psp->pointer[idx].y;
            case RETRO_DEVICE_ID_POINTER_PRESSED:
               if (device == RARCH_DEVICE_POINTER_SCREEN)
                  return (idx < psp->pointer_count) &&
                     (psp->pointer[idx].full_x != -0x8000) &&
                     (psp->pointer[idx].full_y != -0x8000);
               return (idx < psp->pointer_count) &&
                  (psp->pointer[idx].x != -0x8000) &&
                  (psp->pointer[idx].y != -0x8000);
            case RETRO_DEVICE_ID_POINTER_COUNT:
               return psp->pointer_count;

            // このリクエストはぶっちゃけ何なのか良く分らない。
            // android_input.c以外では実装しておらず、唯一のリクエスト元のmenu_input_get_touchscreen_hw_stateでは、
            // これがなんだかわけわからんけどとにかく実装してるという感じのコメントがある。
            // というわけで全くの謎なのだがandroid_input.cの実装によればBackspaceの押下状態を返しているようなのでそれに合わせている。
            // もしかしたらコメントアウトしておいた方が無難かもしれない。
            case RARCH_DEVICE_ID_POINTER_BACK:
            {
               const struct retro_keybind *keyptr = 
                  &input_autoconf_binds[0][RARCH_MENU_TOGGLE];
               // android_input.cの実装によればここで返すべき値はこのソース上での「key_sym」がBackspaceの意味になる時の押下状態のようだ。(何を言ってのるかわからねーと思うが…)
               // key_symの値に関する明確な仕様は確認できなかったのだが、modifier_lutの内容から推測するにUSB HID Usage IDの値が使われるものと思われる。
               // というわけで、USB HID Usage IDでBackspaceを表す0x2Aの押下状態を返しておく。
               if (keyptr->joykey == 0)
                  return psp->keyboard_state[0x2A];	// USB HID Usage ID 0x2A = Backspace
            }
         }
         break;
/* ---------- MOD END ---------- */
#endif
   }

   return 0;
}

ビルド成功!
Vitaへ転送!
ソフトウェアキーボードを設定!
Vita版いきます!

動かない!

そう。これではまだ動かないのである。

嘘だと言ってよ。

なぜ動かないのか

実はだいぶ早い段階で動かないのは知っていたのだが説明の都合上後にしたのだ。なにしろひどくややこしい。
なにがまずかったかと言うと、ここです。

         video_driver_translate_coord_viewport_wrap(
               &vp,
               x, y,
               &psp->pointer[i].x,
               &psp->pointer[i].y,
               &psp->pointer[i].full_x,
               &psp->pointer[i].full_y);
      }

わかるかなーわかんねーだろうなー。
printfデバッグもどきで呼出し後の状態を確認してみると、

  • タッチ座標のx,yはちゃんと取れてる

  • pointer内は全部ゼロ

なんでやねん!

意味が分からないので内部的に呼び出される関数の「video_driver_get_viewport_info」と「video_driver_translate_coord_viewport」を見てみると、video_driver_get_viewport_infoでは

bool video_driver_get_viewport_info(struct video_viewport *viewport)
{
   video_driver_state_t *video_st  = &video_driver_st;
   if (!video_st->current_video || !video_st->current_video->viewport_info)
      return false;
   video_st->current_video->viewport_info(video_st->data, viewport);
   return true;
}

これを追っていくと、「viewport_info」が「vita2d_gfx_viewport_info」の呼び出しになることが分かるのですが、vita2d_gfx_viewport_infoはどうなっているかというと、

static void vita2d_gfx_viewport_info(void *data,
      struct video_viewport *vp)
{
    vita_video_t *vita = (vita_video_t*)data;

    if (vita)
       *vp = vita->vp;
}

細かいロジックはさておいて何をしているかというと(このへんの構造体の連鎖を追うのはかなりめんどい)、呼び出し元のstruct video_viewpoint vp(*vp)にシステムのビューポイント情報(vita->vp)をコピーしてくるという動作になります。
これは何も変な感じはしないな。
printfデバッグでも確認するとvideo_driver_get_viewport_infoの呼び出しはちゃんと成功している。(実はこれがちょっとした罠)
ううむ謎は深まるばかり。

次にvideo_driver_translate_coord_viewportを見てみます。

bool video_driver_translate_coord_viewport(
      struct video_viewport *vp,
      int mouse_x,           int mouse_y,
      int16_t *res_x,        int16_t *res_y,
      int16_t *res_screen_x, int16_t *res_screen_y)
{
   int norm_vp_width         = (int)vp->width;
   int norm_vp_height        = (int)vp->height;
   int norm_full_vp_width    = (int)vp->full_width;
   int norm_full_vp_height   = (int)vp->full_height;
   int scaled_screen_x       = -0x8000; /* OOB */
   int scaled_screen_y       = -0x8000; /* OOB */
   int scaled_x              = -0x8000; /* OOB */
   int scaled_y              = -0x8000; /* OOB */
   if (norm_vp_width       <= 0 ||
       norm_vp_height      <= 0 ||
       norm_full_vp_width  <= 0 ||
       norm_full_vp_height <= 0)
      return false;

   if (mouse_x >= 0 && mouse_x <= norm_full_vp_width)
      scaled_screen_x = ((2 * mouse_x * 0x7fff)
            / norm_full_vp_width)  - 0x7fff;

   if (mouse_y >= 0 && mouse_y <= norm_full_vp_height)
      scaled_screen_y = ((2 * mouse_y * 0x7fff)
            / norm_full_vp_height) - 0x7fff;

   mouse_x           -= vp->x;
   mouse_y           -= vp->y;

   if (mouse_x >= 0 && mouse_x <= norm_vp_width)
      scaled_x        = ((2 * mouse_x * 0x7fff)
            / norm_vp_width) - 0x7fff;
   else
      scaled_x        = -0x8000; /* OOB */

   if (mouse_y >= 0 && mouse_y <= norm_vp_height)
      scaled_y        = ((2 * mouse_y * 0x7fff)
            / norm_vp_height) - 0x7fff;

   *res_x             = scaled_x;
   *res_y             = scaled_y;
   *res_screen_x      = scaled_screen_x;
   *res_screen_y      = scaled_screen_y;
   return true;
}

先ほどコピーしてきたvpとタッチ座標を使って例の専用座標空間(-32727から32727のやつ)に変換しているだけ。

だよな…

…?

まさかエラーリターンしている?

いやいやまさかね、そんな馬鹿なことがあるはずがないよね、だってシステムから返ってきたのを渡してるだけやからね、printfデバッグと…

エラーリターンしとるやないかい!

そうなのである。
まさかは起こせる。
マーフィーの法則「失敗する可能性があるものは失敗する」は永久に不滅です。

そもそもpointerが全部ゼロである時点で気が付くべきであった。
video_driver_get_viewport_infoの失敗以外ではここのエラーリターンしかそうなるケースはないのだからね。
単なる変換ごときでエラーになるはずがないと勝手に思い込んで自ら混乱の渦に飛び込んでしまったようだ。
思い込みから抜け出せずにこの事がすぐにピンと来ないようでは、おそらく私は連鎖性想像力欠如屈辱的バグを生み出す才能があるはずである。(詳しくは内藤寛さんの内藤かんチャンをご覧ください)

さて、でもどうしてエラーリターンするのだ?
エラーになる条件なんてこれしかないんだけどな…

   int norm_vp_width         = (int)vp->width;
   int norm_vp_height        = (int)vp->height;
   int norm_full_vp_width    = (int)vp->full_width;
   int norm_full_vp_height   = (int)vp->full_height;
   ...
   if (norm_vp_width       <= 0 ||
       norm_vp_height      <= 0 ||
       norm_full_vp_width  <= 0 ||
       norm_full_vp_height <= 0)
      return false;

ふうむ…(おもむろにvpの中身をprintfデバッグ…)

「full_width=0,full_height=0」

「full_width=0,full_height=0」

「full_width=0,full_height=0」

思わず小見出しまでつけてしまった。

そうなのである。
full_widthもfull_heightもVita版ではセットされないのだ。
まさかにも限度というものがあろう。
こんなのあんまりだよ。わかるわけがないよ。

しかしもうわかってしまった。
後戻りはできない。
我々は現実と向き合わねばならない。

というわけでどうやって対処するかを考えねばならないわけですが、考えられるのはこのへんでしょうか。

  • video_driver_translate_coord_viewportを呼ぶ前にfull_widthとfull_heightを無理やり初期化してあげる
    video_driver_get_viewport_info呼び出し後にごり押しで初期化してからvideo_driver_translate_coord_viewportを呼べばいちおう動くはず理論。

  • いっそ座標変換を自前でやっちゃえばいいじゃん
    video_driver_translate_coord_viewportの中身は理解したのでvpとか小賢しいこと考えてないで全部自分でやっちゃえよの世界。

  • full_widthとfull_heightをどうやって初期化してるか真面目に調べる
    唯一正しい方法。

最初は1番目でお茶を濁そうと思っていたのですが、ここは乗り掛かった舟、折角なので3番目の正攻法でやってみることに。
ちなみに2番目は1年前に改造した時にどうやら採用した様子で、タッチ座標取得後に突如として不思議な変換処理をする(事情を知らないとひどく謎めいた)コードになってました。
(当時はvpがどうとかまでは追ってなかったと思うのだがとにかく座標変換すれば良いみたいだというのは理解していた様子。タッチの解像度が画面の2倍あるみたいだ、というありがたいコメントだけを残して他には何の説明もないありさまです。どうやら、動いた!やったぜ!で終わったらしい)

full_widthとfull_height探求の旅

例によってまずはgrep。
「gfx/drivers」以下のプラットフォームごとのファイルでfull_xxxの設定を行っているようです。
おっと「gfx/drivers/psp1_gfx.c」なるファイルも発見。

static void *psp_init(const video_info_t *video,
      input_driver_t **input, void **input_data)
{
   /* TODO : add ASSERT() checks or use main RAM if
    * VRAM is too low for desired video->input_scale. */

   int pixel_format, lut_pixel_format, lut_block_count;
   unsigned int red_shift, color_mask;
   void *pspinput           = NULL;
   void *displayBuffer      = NULL;
   void *LUT_r              = NULL;
   void *LUT_b              = NULL;
   psp1_video_t *psp        = (psp1_video_t*)calloc(1, sizeof(psp1_video_t));

   if (!psp)
      return NULL;

   sceGuInit();

   psp->vp.x                = 0;
   psp->vp.y                = 0;
   psp->vp.width            = SCEGU_SCR_WIDTH;
   psp->vp.height           = SCEGU_SCR_HEIGHT;
   psp->vp.full_width       = SCEGU_SCR_WIDTH;
   psp->vp.full_height      = SCEGU_SCR_HEIGHT;

冒頭に以下の定義があるので、PSPの画面の解像度で初期化しているようです。これはまあ想定通り。

#ifndef SCEGU_SCR_WIDTH
#define SCEGU_SCR_WIDTH 480
#endif

#ifndef SCEGU_SCR_HEIGHT
#define SCEGU_SCR_HEIGHT 272
#endif

入力処理のようにここでもVitaのコードを「#ifdef VITA」的なので書き分けてるのかと思いきやそうではなくて「gfx/drivers/vita2d_gfx.c」という専用ファイルがありますね。そしてそっちはgrepで引っ掛かっていない。つまり初期化する気配すらない。ナンデカナー。
あれかな、うっかりかな、それともあえてねのパターン?丹精込めて削除しましたってやつ?丹精込めても削除すな!
PSPでやっとるのにVitaでわざわざやめるとは、もうね、どういう了見かと問い詰めたいね。朝まで生で問い詰めたい。

心を落ち着けて、他のプラットフォームも見てみると、
PS2用の「gfx/drivers/ps2_gfx.c」とか
PS3用?の「gfx/drivers/rsx_gfx.c」とか
WiiU用?の「gfx/drivers/gx2_gfx.c」とか
Switch用の「gfx/drivers/switch_gfx.c」(「gfx/drivers/switch_nx_gfx.c」もSwitch用?)とか
どれも同様にグラフィックス初期化用?の「xxx_init」でfull_xxxを初期化するようになっている模様。

Vita用の「gfx/drivers/vita2d_gfx.c」を見てみると、「vita2d_gfx_init」が初期化関数のようです。
ビューポート関連の初期化は「vita2d_gfx_set_viewport」という関数を呼び出してその中で行っていますが、初期化しているのは「x」「y」「width」「height」のみ。full_xxxは一切出てきません。

static void *vita2d_gfx_init(const video_info_t *video,
      input_driver_t **input, void **input_data)
{
   ...
   vita2d_gfx_set_viewport(vita, temp_width, temp_height, false, true);
   ...
}

static void vita2d_gfx_set_viewport(void *data, unsigned viewport_width,
      unsigned viewport_height, bool force_full, bool allow_rotate)
{
      ...
      vita->vp.x      = x;
      vita->vp.y      = y;
      vita->vp.width  = viewport_width;
      vita->vp.height = viewport_height;
      ...
}

となるとfull_xxxがここで抜けててこの関数内に初期化を追加すればいいのかな?という感じがしますが、これについては「gfx/drivers/rsx_gfx.c」が参考になります。
両者のファイルの最後にあるこれに注目。
まずはVita用。

video_driver_t video_vita2d = {
   vita2d_gfx_init,
   vita2d_gfx_frame,
   vita2d_gfx_set_nonblock_state,
   vita2d_gfx_alive,
   vita2d_gfx_focus,
   vita2d_gfx_suppress_screensaver,
   NULL, /* has_windowed */
   vita2d_gfx_set_shader,
   vita2d_gfx_free,
   "vita2d",
   vita2d_gfx_set_viewport,
   vita2d_gfx_set_rotation,
   vita2d_gfx_viewport_info,
   NULL, /* read_viewport */
   NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
   vita2d_get_overlay_interface,
#endif
#ifdef HAVE_VIDEO_LAYOUT
  NULL,
#endif
   vita2d_gfx_get_poke_interface,
   NULL,
#ifdef HAVE_GFX_WIDGETS
   vita2d_gfx_gfx_widgets_enabled
#endif
};

PS3用。

video_driver_t video_gcm =
{
   rsx_init,
   rsx_frame,
   rsx_set_nonblock_state,
   rsx_alive,
   rsx_focus,
   rsx_suppress_screensaver,
   NULL, /* has_windowed */
   rsx_set_shader,
   rsx_free,
   "rsx",
   rsx_set_viewport,
   rsx_set_rotation,
   rsx_viewport_info,
   NULL, /* read_viewport  */
   NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
   NULL,
#endif
#ifdef HAVE_VIDEO_LAYOUT
  NULL,
#endif
   rsx_get_poke_interface,
   rsx_wrap_type_to_enum,
#ifdef HAVE_GFX_WIDGETS
   rsx_widgets_enabled
#endif
};

ちなみにPSP用はこれ。

video_driver_t video_psp1 = {
   psp_init,
   psp_frame,
   psp_set_nonblock_state,
   psp_alive,
   psp_focus,
   psp_suppress_screensaver,
   NULL, /* has_windowed */
   psp_set_shader,
   psp_free,
   "psp1",
   NULL, /* set_viewport */
   psp_set_rotation,
   psp_viewport_info,
   psp_read_viewport,
   NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
   NULL,
#endif
#ifdef HAVE_VIDEO_LAYOUT
  NULL,
#endif
   psp_get_poke_interface
};

これはRetroarchから呼ばれるグラフィックス関数のエントリリストと思われる構造体ですが、VitaとPS3がset_viewport用のエントリを持っていて他のは(全部は見てないけどほぼ)持っていないので、PS3の方針を真似るのが一番妥当に思われます。

そしてPS3の実装を見ると、

  • rsx_initでfull_width,full_heightを初期化「する」

  • rsx_set_viewportではfull_width,full_heightを変更「しない」

となっています。

static void* rsx_init(const video_info_t* video,
      input_driver_t** input, void** input_data)
{
   ...

   rsx->vp.x = 0;
   rsx->vp.y = 0;
   rsx->vp.width = rsx->width;
   rsx->vp.height = rsx->height;
   rsx->vp.full_width = rsx->width;
   rsx->vp.full_height = rsx->height;
   rsx->rgb32 = video->rgb32;
   video_driver_set_size(rsx->vp.width, rsx->vp.height);
   rsx_set_viewport(rsx, rsx->vp.width, rsx->vp.height, false, true);

   ...
}

static void rsx_set_viewport(void *data, unsigned viewport_width,
      unsigned viewport_height, bool force_full, bool allow_rotate)
{
      ...
      rsx->vp.x      = x;
      rsx->vp.y      = y;
      rsx->vp.width  = viewport_width;
      rsx->vp.height = viewport_height;
      ...
}

つまり、Vita版でもvita2d_gfx_initにて初期化するのが正しいはずで、vita2d_gfx_set_viewportで設定してはいけないという事になります。

これを踏まえると「gfx/drivers/vita2d_gfx.c」はこうなるッ!

static void *vita2d_gfx_init(const video_info_t *video,
      input_driver_t **input, void **input_data)
{
   vita_video_t *vita   = (vita_video_t *)calloc(1, sizeof(vita_video_t));

   ...

   vita->video_width  = temp_width;
   vita->video_height = temp_height;

/* ---------- MOD BEGIN ---------- */
   // Vita版ではfull_width, full_heightが初期化されていない。理由は不明だがたぶん使わないからサボってるだけだと思う。
   // この影響でvideo_driver_translate_coord_viewport_wrap(タッチ操作で必要な座標系に変換するやつ)が正常動作しない。
   // 自前で座標変換してもいいのだがどちらかと言えばこれを使った方が良いはずなので初期化を追加して使えるようにしておく。
   //
   // 他のプラットフォームでのfull_xxxに関する実装を確認すると、
   //  ・initで初期化
   //  ・set_viewpointでは更新しない(たぶんこれは外部から呼ばれる可能性がある)
   // という方針のようなのでこれに倣ってinit内に初期化を追加。
   //
   // ※当初は名前からしてset_viewpoint内で初期化するのかと思っていたのだがどうも違うようだ。(実装を見るとエミュ画面部分のサイズ変更用とかそんな感じっぽい)
   //   現状ではどちらに書いても動作は同じなのだがおそらく意味的に変になるのでここで初期化する。
   vita->vp.full_width  = PSP_FB_WIDTH;
   vita->vp.full_height = PSP_FB_HEIGHT;
/* ---------- MOD END ---------- */

   video_driver_set_size(temp_width, temp_height);
   vita2d_gfx_set_viewport(vita, temp_width, temp_height, false, true);

   ...
}

改造リターンズ

時は来たそれだけだ。
颯爽とビルドを終えてVitaへ転送。
いざRetroarch起動!

「full_width=960,full_height=544」!!!

ついにッ!フルなんとかがッ!取れましたよッ!
時はは来たそれだけだだだ。
すみません取り乱してしまいました。

ついにその時が訪れてしまった。
これでタッチが使えるようになっているはずだッ!

うおおおお!こいつうごくぞ!

よっしゃーーー!寄進しちゃおっかなっ!

えいやーーーーッ!

…!

……!!

ちくしょーーおおおおーーーーーーっ!!!

なんなんだこいつら。
俺に恨みでもあるのか。

次回予告

念願のソフトウェアキーボードを手に入れたはずでしたが、なぜかテンキーが押せないという、マーフィーですらそんな法則知らんと言ってしまうレベルの失敗続きに見舞われた私は、ついにソフトウェアキーボードオーバーレイの深淵までをも覗き込む羽目になるのであった。
次回、オーバーレイの冒険の旅、絶対に見てくれよななんて言えないよ絶対。
もうちょっとだけ続くんじゃ。

この記事が気に入ったらサポートをしてみませんか?