Archives

August 2011 の記事

引き続き Android OS 3.1 に Xbox360 Pad と Playstation3 Pad を
つないだ場合の値の取り方です。(前回)
アナログスティックの情報は onGenericMotionEvent で受け取れます。

@Override
public boolean onGenericMotionEvent( MotionEvent event ) {
  float posx= event.getAxisValue( MotionEvent.AXIS_X );
  ~
}

入力値に差が生じた場合のみイベントが発生します。
getX()/getY() は getAxisValue() の AXIS_X, AXIS_Y と同値です。

対応付は下記の通り

axis            Xbox360Pad   PS3 Pad       範囲  Source
-----------------------------------------------------------------------
AXIS_X          左stick 横   左stick 横   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_Y          左stick 縦   左stick 縦   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_Z          右stick 横   右stick 横   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_RZ         右stick 縦   右stick 縦   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_HAT_X      十字キー横   ----         -1~1  SOURCE_CLASS_JOYSTICK
AXIS_HAT_Y      十字キー縦   ----         -1~1  SOURCE_CLASS_JOYSTICK
AXIS_LTRIGGER   Lトリガ      L2            0~1
AXIS_RTRIGGER   Rトリガ      R2            0~1

左上が (-1,-1), 右下が (1,1)

Xbox360 Pad のみ、デジタル方向ボタン(十字キー) が MotionEvent の
AXIS_HAT_X/AXIS_HAT_Y を返します。(KeyEvent も発生します)

他にも MotionEvent と同時に KeyEvent を発生させるものがあります。

◎Xbox360 Pad / PS3 Pad
・AXIS_X           KEYCODE_DPAD_LEFT/KEYCODE_DPAD_RIGHT
・AXIS_Y           KEYCODE_DPAD_UP/KEYCODE_DPAD_DOWN

◎PS3 Pad のみ
・AXIS_LTRIGGER    KEYCODE_L2
・AXIS_RTRIGGER    KEYCODE_R2

◎Xbox360 Pad のみ
・AXIS_HAT_X       KEYCODE_DPAD_LEFT/KEYCODE_DPAD_RIGHT
・AXIS_HAT_Y       KEYCODE_DPAD_UP/KEYCODE_DPAD_DOWN


●デジタル方向キーとアナログスティックの区別方法

ここで問題となるのは、左アナログスティックで MotionEvent だけでなく
KeyEvent も発生してしまうことです。
デジタル方向ボタンの代わりに左アナログスティックを使うことができる反面、
アナログとデジタルに異なる操作を割り当てたいときに困ります。


KeyCode だけではこの両者を区別できないので方法を探しました。

Xbox360 Pad の方向キーは AXIS_HAT_X/AXIS_HAT_Y からも値をとれるため
KeyEvent を参照せずにこちらを使えば区別することができます。


PS3 Pad の場合は SOURCE_CLASS で区別できるようです。

訂正 2011/08/11 不要でした。Xbox360 のみ AXIS → KeyCode 変換だけで OK です。

KeyEvent.getSource() & InputDevice.SOURCE_CLASS_MASK

PS3 のみデジタル方向キーが SOURCE_CLASS_BUTTON となります。

◎KeyEvent の SOURCE_CLASS 値

                    Xbox360 Pad              PS3 Pad
--------------------------------------------------------------------
デジタル方向キー    SOURCE_CLASS_JOYSTICK    SOURCE_CLASS_BUTTON
アナログ方向キー    SOURCE_CLASS_JOYSTICK    SOURCE_CLASS_JOYSTICK


◎MotionEvent の情報

                    Xbox360 Pad              PS3 Pad
--------------------------------------------------------------------
デジタル方向キー    AXIS_HAT_X/AXIS_HAT_Y    無し
アナログ方向キー    AXIS_X/AXIS_Y            AXIS_X/AXIS_Y

実際のコード(Activity 抜粋)は下記の通り。
これで Xbox360 Pad / PS3 Pad 共に、デジタル方向ボタンを
KEYCODE_DPAD_UP/DOWN/LEFT/RIGHT を受け取れるようになります。
(プログラムコード訂正しました 2011/08/11 )

final static int   SENDKEY_UP      =   0;
final static int   SENDKEY_DOWN    =   1;

private int mPrevState= 0;

private boolean sendKey( int KeyCode, int up_down ) {
   // 実際のキーコード処理

   ~
}

@Override
public boolean onKeyDown( int KeyCode, KeyEvent event ) {
    return  sendKey( KeyCode, SENDKEY_DOWN );
}

@Override
public boolean onKeyUp( int KeyCode, KeyEvent event ) {
    return  sendKey( KeyCode, SENDKEY_UP );
}

@Override
public boolean onGenericMotionEvent( MotionEvent event ) {
    // Xbox360 の 十字キーはこれで判別
    final int   KEYFLAG_UP      = (1<<0);
    final int   KEYFLAG_DOWN    = (1<<1);
    final int   KEYFLAG_LEFT    = (1<<2);
    final int   KEYFLAG_RIGHT   = (1<<3);
    final float HAT_BORDER= 0.5f;

    float hatx= event.getAxisValue( MotionEvent.AXIS_HAT_X );
    float haty= event.getAxisValue( MotionEvent.AXIS_HAT_Y );

    int code= 0;
    if( hatx < -HAT_BORDER ){
        code|= KEYFLAG_LEFT;
    }
    if( hatx >  HAT_BORDER ){
        code|= KEYFLAG_RIGHT;
    }
    if( haty < -HAT_BORDER ){
        code|= KEYFLAG_UP;
    }
    if( haty >  HAT_BORDER ){
        code|= KEYFLAG_DOWN;
    }

    int diff= code ^ mPrevState;
    mPrevState= code;
    for( int kc= KeyEvent.KEYCODE_DPAD_UP ; diff != 0 ; diff>>= 1, code>>= 1, kc++ ){
        if( (diff & 1) != 0 ){
            // 代わりのキーコードを発生させる
            sendKey( kc, (code & 1) != 0 ? SENDKEY_DOWN : SENDKEY_UP );
        }
    }


    // アナログ値の受け取り
    float lx= event.getAxisValue( MotionEvent.AXIS_X );
    float ly= event.getAxisValue( MotionEvent.AXIS_Y );
    float rx= event.getAxisValue( MotionEvent.AXIS_Z );
    float ry= event.getAxisValue( MotionEvent.AXIS_RZ );
    float tl= event.getAxisValue( MotionEvent.AXIS_LTRIGGER );
    float tr= event.getAxisValue( MotionEvent.AXIS_RTRIGGER );

    ~
}


●複数のパッドの認識

USB ハブを経由して、同時に複数のコントローラを接続することができました。
どのコントローラも上記の同じイベントを発生させます。

getDeviceId() を参照することで、どのコントローラが送ったイベントなのか
区別できるようです。

Xbox360 Pad と PS3 Pad の組み合わせは OK、PS3 Pad を同時に 2個つないでも
きちんと区別できました。バスパワーハブだったため 3個以上は試していません。


● Playstation3 Pad (PS3 Pad) の注意点

PS3 Pad はもともとワイヤレスコントローラですが、Android 3.1 の場合
USB ケーブルで繋ぎます。

 1. PS3 Pad が電源 OFF の状態を確認
 2. USB ケーブルで Android 3.1 端末に繋ぐ
 3. (PS) ボタンを押す
 4. LED 4つが点滅したままだけど問題なし。そのまま使える。

接続前に (PS) ボタンを押してしまうとペアリングしてある PS3 本体の
電源が入ってしまうので要注意です。
ワイヤレスで PS3 本体につながってしまうと USB ケーブルをつないでも
PS3 が優先され Android で認識できません。
最初の頃、遠くにある PS3 本体の電源がいつの間にか入っていて、
なかなかつながらずにはまりました。


関連エントリ
Android 3.1 と GamePad のイベントコード


Acer ICONIA TAB A500 が Android OS 3.1 になったので早速つないでみました。
Android 3.1 からは USB でゲームパッドがつながります。

 ・Xbox 360 用の USB コントローラ
 ・Playstation3 用コントローラを USB (有線)接続

どちらもきちんと認識しました。
判定の仕方は下記のとおりです。

ボタン類は通常のキーボードと同じ、onKey~ でキーコードとして通知されます。

十字キーはカーソルキーと同じ KEYCODE_DPAD_UP/DOWN/LEFT/RIGHT
(19~22) です。
そのため取り敢えずつないだけでも HOME 画面などでカーソル移動に使えます。
未確認ですが、おそらく KeyEvent.isGamepadButton() で本来のカーソルキーと
区別できると思われます。
訂正2011/08/10 13:22

ボタン類は専用のキーコード(96~)が割り当てられているようです。

KEYCODE_BUTTON_A        96  □
KEYCODE_BUTTON_B        97  △
KEYCODE_BUTTON_C        98
KEYCODE_BUTTON_X        99  ×
KEYCODE_BUTTON_Y       100  ◯
KEYCODE_BUTTON_Z       101
KEYCODE_BUTTON_L1      102
KEYCODE_BUTTON_R1      103
KEYCODE_BUTTON_L2      104
KEYCODE_BUTTON_R2      105
KEYCODE_BUTTON_THUMBL  106
KEYCODE_BUTTON_THUMBR  107
KEYCODE_BUTTON_START   108
KEYCODE_BUTTON_SELECT  109  (BACK)
KEYCODE_BUTTON_MODE    110  (Xbox)
                       188  (PS)

Xbox360 と PS3 ほぼ上記のキーアサイン通りなのですが微妙に互換性がありません。
BUTTON_X は 360 だと (X) ボタンで PS3 は (×) バツボタンです。
AB/XY が入れ替わっているような感じです。
360 Pad 中央の Xbox ボタンは KEYCODE_BUTTON_MODE ですが、
PS ボタンは異なる数値 (188) でした。

アナログ操作は onGenericMotionEvent で MotionEvent が来ます。
L/Rトリガは 360 は MotionEvent だけですが PS3 だと
MotionEvent + BUTTON_L2/R2 の両方が返るようです。

もう少し調査が必要ですが、取り敢えず PC 等と操作方法の
互換性が保てるので便利になりそうです。


そこそこ複雑なシーンでの比較。

Mali-400MP  800x480 : 50fps
Adreno 205  854x480 : 20fps   
Adreno 200  800x480 :  5fps
PVR SGX540  800x480 : 23fps

そこそこの背景+キャラ描画に Shadow map ありの数値です。
TBR だし OpenGL ES 2.0 では Shadow map は現実的ではないと
思ってましたが Mali だと十分できそうです。
影なしだと全体的に数値が上がり、Mali はほとんど 60fps 固定。
PVR 対策で alpha test は off。
圧縮テクスチャ次第ではもう少し差が縮まるかもしれません。

Tegra2 は解像度が異なり近い条件での比較ができませんでした。
Tegra2 には depth texture がありませんが、代わりに half float
の color buffer を使っています。

if( Extension("GL_OES_depth_texture") ){
  ~
}else if( Extension("GL_OES_texture_half_float") ){
  // Tegra250
  glGenTextures( 1, &gShadowTexture );
  glBindTexture( GL_TEXTURE_2D, gShadowTexture );
  glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_HALF_FLOAT_OES, NULL );

  glGenRenderbuffers( 1, &gShadowDepth );
  glBindRenderbuffer( GL_RENDERBUFFER, gShadowDepth );
  glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height );

  glGenFramebuffers( 1, &gShadowTarget );
  glBindFramebuffer( GL_FRAMEBUFFER, gShadowTarget );
  glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gShadowTexture, 0 );
  glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gShadowDepth );
}

GL ES Extension 一覧


関連エントリ
Android Galaxy S2 ARM Mali-400 MP


ベンチは走らせてませんが 3D 描画は速い。
今まで試した Android の中では最速でした。

● GPU

GL_VERSION: OpenGL ES 2.0
GL_RENDERER: Mali-400 MP
GL_VENDOR: ARM
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL ES 1.00

[GL_OES_texture_npot]
[GL_OES_compressed_ETC1_RGB8_texture]
[GL_OES_standard_derivatives]
[GL_OES_EGL_image]
[GL_OES_depth24]
[GL_ARM_rgba8]
[GL_ARM_mali_shader_binary]
[GL_OES_depth_texture]
[GL_OES_packed_depth_stencil]

API=0  Shader=100
pconst=1024 vconst=128 vin=16 vout=12 ptex=8 vtex=0 combotex=8 maxrender=4096 maxtexsize=4096 cubetexsize=1024 viewdims=4096

TextureFormat 1
tc[00]=8d64  GL_ETC1_RGB8_OES

Unified でなく unit が独立しているせいもあるかもしれませんが、
比較的長い pixel shader も意外なほど動きます。

その代わり extension は少な目です。

Mobile GPU の比較

float 系テクスチャや圧縮頂点フォーマットが無いようです。
また圧縮テクスチャが ETC1 だけに制限されるのも、他の GPU との
大きな違いとなっています。

今の Mobile GPU はそれぞれ何かしらのトレードオフがあり全部入りはありません。

例えば Adreno 200 は演算ユニットが高精度固定、Unified で頂点機能も豊富、
低帯域対策は TBR と ATITC。その代わり頂点キャッシュなし。
PVR SGX535 は TBDR + PVRTC で全体的なスループットが高いがシェーダーの
演算精度をどこまで落とせるかが最適化の要。
Tegra2 は TBR でない代わりに転送量削減で 16bit depth 固定など。

Mali-400MP はまた違った特徴を持っているようです。


● CPU

Cortex-A9 1.2GHz dual。iPad2 同様 neon ありです。
今まで触ったハードで A9 neon 無しは Tegra2 だけでした。

Processor	: ARMv7 Processor rev 1 (v7l)
processor	: 0
BogoMIPS	: 1592.52

processor	: 1
BogoMIPS	: 1592.52

Features	: swp half thumb fastmult vfp edsp neon vfpv3 
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x2
CPU part	: 0xc09
CPU revision	: 1

モバイルでの 3D ゲームも現実的なレベルだと思います。
デスクトップ GPU との差はまだまだですが、意外にすぐ追いつかれるかも
しれません。


関連エントリ
iPad2 A5 と浮動小数演算 VFP/NEON
Android OpenGL ES 2.0 の圧縮テクスチャ
Tegra2 Cortex-A9 と浮動小数演算
Snapdragon と浮動小数演算速度