Archives

February 2014 の記事

Linux では接続した Gamepad の情報を /dev/input/js* から読み取ることができます。
構造体などは /usr/include/linux/joystick.h で定義されており、
具体的な方法は下記ドキュメントの通り。

joystick-api.txt

int fd= open( "/dev/input/js0", O_RDONLY );

unsigned char  ButtonData[BUTTON_DATA_MAX];
signed int     StickData[STICK_DATA_MAX];

for(;;){
    struct js_event  event;
    if( read( fd, &event, sizeof(struct js_event) ) >= sizeof(struct js_event) ){
        switch( event.type & 0x7f ){
        case JS_EVENT_BUTTON:
            if( event.number < BUTTON_DATA_MAX ){
                ButtonData[ event.number ]= event.value != 0;
            }
            break;
        case JS_EVENT_AXIS:
            if( event.number < STICK_DATA_MAX ){
                StickData[ event.number ]= event.value;
            }
            break;
        }
    }
}
close( fd );

read() はイベントが発生するまで block するので、スレッドを使うか
他の IO のように select() を用いることが可能です。

ボタンやスティックの配列はコントローラ毎に異なっています。
アプリケーション側で配列の変更が必要です。
Windows の DirectInput で認識する配列とも異なっているようです。

とりあえず下記コントローラで動作を確認しました。

・PS3 コントローラ (USB 接続)
・Xbox 360 USB コントローラ
・PS4 DUALSHOCK4 (USB 接続)

PS3コントローラは各ボタンの感圧情報も送られてきます。
使用した環境は下記の通り。
Ubuntu 13.10 x86_x64 (64bit)


● デバイスの種類の判定

自分の環境では Gamepad だけでなく、ワイヤレスマウスのレシーバーも
/dev/input/js* に列挙されていました。
そのままでは不便なので、Gamepad かどうかの識別を行います。

udevadm info /dev/input/js0

上記コマンドでは正しく ID_INPUT_JOYSTICK と識別されているので、
udevadm の手法を調べてみました。
まず stat() で /dev/input/js* の device 番号を調べます。

struct stat  file_stat;
stat( "/dev/input/js0", &file_stat );
assert( S_ISCHR( file_stat.st_mode ) );
int dev_major= major( file_stat.st_rdev );
int dev_minor= minor( file_stat.st_rdev );

キャラクタデバイスなら "/sys/dev/char/" 以下に "major:minor" という
名前でリンクが作られているので、それを開きます。
その中の device/capabilities 以下に、対応しているボタンなどの
情報が格納されています。

例えば major,minor が 13,0 なら
/sys/dev/char/13:0/device/capabilities/key

中身は text で long 型の bit mask です。
udevadm では、この bitmask を読み取ってゲームコントローラ用のボタンが
存在してるかどうかで判別を行っていました。
ボタンなどのシンボルは /usr/include/linux/input.h で定義されています。

static bool ReadCaps( int dev_major, int dev_minor, const char* file_name, CapsType& caps )
{
    caps.Clear();
    char cap_name[CAP_NAME_MAX];
    sprintf_s( cap_name, CAP_NAME_MAX, "/sys/dev/char/%d:%d/device/capabilities/%s", file_name, dev_major, dev_minor );
    int fd= open( cap_name, O_RDONLY );
    if( fd < 0 ){
        return  false;
    }
    char    caps_buffer[STAT_MAX_SIZE];
    memset( caps_buffer, 0, STAT_MAX_SIZE );
    read( fd, caps_buffer, STAT_MAX_SIZE-1 );
    close( fd );

    const char* cp= caps_buffer;
    for(; *cp && *cp != '\n' ;){
        unsigned long   value= strtoul( cp, NULL, 16 );
        for(; *cp && *cp != ' ' ; cp++ );
        for(; *cp == ' ' || *cp == '\n' ; cp++ );
        caps.Push( value );
    }
    return  true;
}

bool IsJoystick( const char* device_path )
{
    struct stat  file_stat;
    stat( device_path, &file_stat );
    assert( S_ISCHR( file_stat.st_mode ) );
    int dev_major= major( file_stat.st_rdev );
    int dev_minor= minor( file_stat.st_rdev );

    CapsType  caps_ev;
    CapsType  caps_key;
    CapsType  caps_abs;
    ReadCaps( dev_major, dev_minor, "ev", caps_key );
    ReadCaps( dev_major, dev_minor, "key", caps_key );
    ReadCaps( dev_major, dev_minor, "abs", caps_key );

    if( caps_ev.TestBit( EV_ABS ) && caps_abs.TestBit( ABS_X ) && caps_abs.TestBit( ABS_Y ) ){
        if( cap_key.TestBit( BTN_A ) || caps_key.TestBit( BTN_TRIGGER ) || caps_key.TestBit( BTN_1 ) ){
            return  true;
        }
    }
    return  false;
}

abs に ABS_X / ABS_Y のアナログの絶対座標データが含まれており、
かつ key に BTN_A / BTN_TRIGGER / BTN_1 のいずれかが存在している場合に
JOYSTICK とみなしています。

class CapsType {
public:
    enum {
        CAPS_BUFFER_MAX= 32,
    };
private:
    unsigned long   CapsBuffer[CAPS_BUFFER_MAX];
    unsigned int    Index;
public:
    CapsType() : Index( 0 )
    {
        Clear();
    }
    void Clear()
    {
        memset( CapsBuffer, 0, sizeof(unsigned long) * CAPS_BUFFER_MAX );
        Index= 0;
    }
    void Push( unsigned long value )
    {
        if( Index < CAPS_BUFFER_MAX ){
            CapsBuffer[Index++]= value;
        }
    }
    bool TestBit( unsigned int bit_id ) const
    {
        const int   LONG_BIT_SIZE= sizeof(unsigned long) * 8;
        unsigned int    bit_no= bit_id & (LONG_BIT_SIZE-1);
        unsigned int    data_no= bit_id / LONG_BIT_SIZE;
        if( data_no < Index ){
            return  ( CapsBuffer[Index - data_no -1] & (1UL<<bit_no) ) != 0;
        }
        return  false;
    }
};

Linux は LP64 なので long 型のサイズは可変です。
64bit OS なら 64bit, 32bit なら 32bit となる点に注意。
32bit 時は CAPS_BUFFER_MAX が不足する可能性があるため、厳密には
/usr/include/linux/input.h で定義されている EV_MAX, KEY_MAX, ABS_MAX
からバッファサイズを求めた方が安全です。
bitmask は上位データから並んでいるので、読み込んだあとのテーブルが
逆順になる点も注意が必要です。


関連エントリ
Linux 他 X11 で OpenGL 4 を使う
Android 3.1 と GamePad のイベントの詳細 (2)
Android 3.1 と GamePad のイベントコード


昔作成した 3D ポリゴンのゲームを移植してみました。
CPU だけでレンダリングしているので、3D GPU が入っていない Z Watch でも動きます。

zwatch_chiraks01.png

ChiRaKS (Android)

当時 (2000年) は SH3 30~60MHz の SHARP MI-Zaurus (PDA) で動いており
160x160 サイズのレンダリングでぎりぎり 30fps に届かない程度でした。

その後移植した PocketPC (WindowsCE/WindowsMobile) では、すでに iPAQ に
StrongARM (206MHz) が搭載されており 100fps も余裕で超えています。
同世代の MIPS VR4122 端末 (150MHz) は StrongARM の 1/3 くらいの速度でした。

現在のハイエンド端末は single core でも Zaurus の 100倍以上速いので
1080x1080 サイズのレンダリングでも 60fps 近く出ます。
ただし 32bit 固定小数点演算を行っている関係上、演算がオーバーフローしてしまう
問題があったため、アプリでは最大解像度を 540x540 に制限しています。

Z Watch (JZ4775 MIPS) の画面は 240x240 と小さいこともあり余裕です。
設定の NoWait にチェックを入れると 60fps 出ていることがわかります。

レンダリングには NDK の Bitmap API を利用しています。

#include  <android/bitmap.h>
~
void Render( JNIEnv* env, jobject obj, jobject bitmap_object )
{
    void* ptr= NULL;
    AndroidBitmap_lockPixels( env, bitmap_object, &ptr );
    ~
    AndroidBitmap_unlockPixels( env, bitmap_object );
}


関連エントリ
Android SmartWatch スマートウオッチのスペック比較表
Android SmartWatch SmartQ ZWatch (4) アプリの管理
Android SmartWatch SmartQ ZWatch (3) 腕に関数電卓
Android SmartWatch SmartQ ZWatch (2)
Android 4.1 SmartWatch SmartQ Z Watch


ARM Cortex-A7 は big.LITTLE で用いられる場合、省電力側の CPU core に相当します。
消費電力が少ない代わりに、高性能側の CPU core より性能は劣ります。
VFP Benchmark の結果を送ってもらいました。

結果を見ると、Cortex-A7 の NEON は 32bit 単位で実行していることがわかります。
演算速度は NEON 無しの CPU と変わらないのですが、Cortex-A15 のペアとして
機能できるよう NEON 命令セットに対応しているものと考えられます。

SIMD (Vector)         SIMD4 single fp (32bit x4)
CPU                   mul    add     mad     fma
------------------------------------------------------
ARM Cortex-A7         1      1       2       2
ARM Cortex-A8         2      2       4       –
ARM Cortex-A9         2      2       4       –
ARM Cortex-A15        4      4       8       8
Qualcomm Scorpion     4      4       8       –
Qualcomm Krait 400    4      4       8       8
Apple A6 Swift        4      4       8       8
Apple A7 Cyclone 32   8      12      16      16
Apple A7 Cyclone 64   8      12      –       16

  * 数値は 1 cycle あたりの演算数、大きい方が速い

FPU の演算能力 (上記以外の結果はこちら)

big.LITTLE で用いる場合は、この手の演算は Cortex-A15 の担当なので
おそらく表に出ることは無いでしょう。
単独で用いられている場合は、同じ Quad core CPU と書かれていても
かなり性能差が開いていることを考慮した方が良いかもしれません。
浮動小数点演算速度だけ見てもピーク演算速度で Cortex-A9 の半分、
Krait/Cortex-A15 の 1/4 (同一クロック時) となっています。

以下詳細

Lenovo YOGA TABLET 8 (3G)
MediaTek MT8389 1.2GHz Cortex-A7 Quad core

SingleT SP max: 2.374 GFLOPS
SingleT DP max: 1.165 GFLOPS
MultiT  SP max: 9.474 GFLOPS
MultiT  DP max: 4.653 GFLOPS

* VFP/NEON (single fp)
VFP fmuls (32bit x1) n8       :    3.634     1100.7     1100.7
VFP fadds (32bit x1) n8       :    3.450     1159.3     1159.3
VFP fmacs (32bit x1) n8       :    3.451     2318.1     2318.1
VFP vfma.f32 (32bit x1) n8    :    3.448     2319.9     2319.9
NEON vmul.f32 (32bit x2) n8   :    6.795     1177.3     1177.3
NEON vadd.f32 (32bit x2) n8   :    6.828     1171.7     1171.7
NEON vmla.f32 (32bit x2) n8   :    6.810     2349.6     2349.6
NEON vfma.f32 (32bit x2) n8   :    6.797     2354.1     2354.1
NEON vmul.f32 (32bit x4) n8   :   13.529     1182.7     1182.7
NEON vadd.f32 (32bit x4) n8   :   13.511     1184.2     1184.2
NEON vmla.f32 (32bit x4) n8   :   13.498     2370.7     2370.7
NEON vfma.f32 (32bit x4) n8   :   13.549     2361.8     2361.8

倍精度の場合はさらに差が広がっており、加算は 1 cycle で実行できますが乗算は 4 倍低速です。
さらに fmacd (積和) は乗算と同等の速度で演算しているものの、
vfma (FMA) は並列化されておらず 5倍 (1 add + 4 mul cycle) かかっているようです。

* VFP/NEON (double fp)
VFP fmuld (64bit x1) n8       :   13.628      293.5      293.5
VFP faddd (64bit x1) n8       :    3.439     1163.0     1163.0
VFP fmacd (64bit x1) n8       :   13.508      592.2      592.2
VFP vfma.f64 (64bit x1) n8    :   16.895      473.5      473.5
VFP fmuld (64bit x1) ns4      :   13.434      297.8      297.8
VFP faddd (64bit x1) ns4      :    3.435     1164.6     1164.6
VFP fmacd (64bit x1) ns4      :   13.430      595.7      595.7
VFP vfma.f64 (64bit x1) ns4   :   16.823      475.5      475.5
VFP fmuld (64bit x1) n1       :   13.439      297.6      297.6
VFP faddd (64bit x1) n1       :    3.447     1160.6     1160.6
VFP fmacd (64bit x1) n1       :   26.856      297.9      297.9
VFP vfma.f64 (64bit x1) n1    :   26.860      297.8      297.8


関連エントリ
VFP Benchmark v1.1 浮動小数点演算命令の速度 (NEON/SSE/AVX)
ARM CPU の VFP Benchmark アプリ 浮動小数点演算速度の計測
iPhone 5s A7 CPU の浮動小数点演算速度 (2) (arm64/AArch64/64bit)
iPhone 5s A7 CPU の浮動小数点演算速度 (32bit)
Nexus 10 CPU Cortex-A15 の速度
Nexus 10 CPU Cortex-A15 の浮動小数点演算速度
Qualcomm APQ8064 GPU Adreno 320 の速度
benchmark 関連


X11 (X window) では Platform Interface に GLX を用います。
Display が本当に意味を持つのは X11 ならではで、egl の API 仕様も
X window のプログラムを見ると納得です。

Display* display= NULL;
int      screen= 0;

display= XOpenDisplay( NULL );
screen= DefaultScreen( display );

(1) pixel format の選択

static int attrib_list[]= {
    GLX_RENDER_TYPE,    GLX_RGBA_BIT,
    GLX_DRAWABLE_TYPE,  GLX_WINDOW_BIT,
    GLX_DOUBLEBUFFER,   True,
    GLX_RED_SIZE,       8,
    GLX_GREEN_SIZE,     8,
    GLX_BLUE_SIZE,      8,
    GLX_ALPHA_SIZE,     8,
    GLX_DEPTH_SIZE,     24,
    GLX_STENCIL_SIZE,   8,
    None
};
int  n_items= 0;
GLXFBConfig*  config_array= glXChooseFBConfig( display, screen, attrib_list, &n_items );
〜
if( n_items ){
    GLXFBConfig fb_config= config_array[0];
    〜
}
XFree( config_array );

サーバーが対応しているピクセルフォーマットから必要なものを選択しています。
フォーマットの組み合わせは GPU やドライバによって異なります。

X native の API にも似たような仕組みとして XVisualInfo があります。
GLXFBConfig は GLX による拡張で、XVisualInfo* の代わりに用いることが可能です。

Windows でも Win32 API に PIXELFORMATDESCRIPTOR (ChoosePixelFormat())
がありました。WGL Extension の WGL_ARB_pixel_format
(wglChoosePixelFormatARB()) はこれを置き換えるもので、
XVisualInfo と GLXFBConfig との関係に似ています。

もし XCreateColormap などで Visual (XVisualInfo) が必要になったら
下記のように取得すれば OK です。

// GLXFBConfig -> XVisualInfo
XVisualInfo* visual_info= glXGetVisualFromFBConfig( display, fb_config );
〜
XFree( visual_info );

上の visual_info は config_array 同様 XFree() が必要です。
egl のような wrapper を作る場合、メモリ管理を伴うのは少々都合が悪い
場合があります。
GLXFBConfig をそのまま保持せずに、ID に紐付けておくと管理が楽になります。

// GLXFBConfig -> fb_config_id
int  fb_config_id= 0;
glXGetFBConfigAttrib( display, fb_config, GLX_FBCONFIG_ID, &fb_config_id );

この fb_config_id があれば GLXConfig が一意に定まるわけです。
fb_config_id から GLXFBConfig を取り出す方法は下記の通り。

// fb_config_id -> GLXFBConfig
int attr_list[]= { GLX_FBCONFIG_ID, fb_config_id, None };
int n_items= 0;
GLXFBConfig* fb_config_ptr= glXChooseFBConfig( display, screen, attr_list, &n_items );
assert( n_items == 1 );
GLXFBConfig fb_config= fb_config_ptr[0];
〜
XFree( fb_config_ptr );

XVisualInfo を得る方法はすでに述べた通りですが、GLXFBConfig から
直接 VisualID を求めることもできます。

// GLXFBConfig -> VisualID
int  visual_id= 0;
glXGetFBConfigAttrib( display, config_array[0], GLX_VISUAL_ID, &visual_id );


(2) Context の作成

任意の Version の OpenGL Context を作成するには GLX_ARB_create_context
( glXCreateContextAttribsARB() ) が必要です。
試した下記の環境ではデフォルトで OpenGL 4.3 が選択されていたので、
場合によっては使わなくても問題ないかもしれません。

・Ubuntu 13.10 desktop x86_64
・GeForce GTX650
・NVIDIA binary Xorg driver 319

Core Profile のみ、OpenGL ES 2.0 互換 Profile の指定など、細かい設定を
行うならやはり glXCreateContextAttribsARB() が必要です。

// Extension API glXCreateContextAttribsARB() の取り出し
PFNGLXCREATECONTEXTATTRIBSARBPROC f_glXCreateContextAttribsARB=
  (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress(
       (const GLubyte*)"glXCreateContextAttribsARB" );

// OpenGL 4.3 context 作成
static int  att_command[]= {
    GLX_CONTEXT_MAJOR_VERSION_ARB,  4,
    GLX_CONTEXT_MINOR_VERSION_ARB,  3,
    GLX_CONTEXT_FLAGS_ARB,          0,
    GLX_CONTEXT_PROFILE_MASK_ARB,   GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
    None
};
GLXContext  context= f_glXCreateContextAttribsARB( display, fb_config, 0, True, att_command );

他にも glX extension を利用する場合が考えられるので、
API の取得はまとめて管理した方が良いでしょう。
glX extension だけでなく、GL 4.x の各命令でも API の取得が必要になります。

4番目の True は Direct Rendering の指定です。
本来の X protocol はネットワーク透過なのでアプリケーションからドライバに
直接アクセスできませんが、ローカルサーバー利用時は条件によって可能になります。
現在の context が Direct Rendering 可能な状態 (DRI) かどうかは
glXIsDirect() で確認することができます。

GL_ARB_create_context


(3) Window の作成

GLXFBConfig が決まった時点で Window を作れるので (2) との順番は任意。

XVisualInfo* visual_info= glXGetVisualFromFBConfig( display, fb_config );
Window  root= RootWindow( NativeDisplay, screen );
XSetWindowAttributes    attr;
attr.background_pixel= 0;
attr.border_pixel= 0;
attr.colormap= XCreateColormap( display, root, visual_info->visual, AllocNone );
attr.event_mask= 0
    |StructureNotifyMask
    |ExposureMask
    |KeyPressMask
    |KeyReleaseMask
    |ButtonPressMask
    |ButtonReleaseMask
    ;
Window	win= XCreateWindow( display, root, x, y, width, height,
    0,	// border
    visual_info->depth, InputOutput, visual_info->visual,
    CWBorderPixel|CWColormap|CWEventMask, &attr );
XFree( visual_info );
visual_info= NULL;

XMapWindow( display, win );
XFlush( display );


(4) MakeCurrent

glXMakeCurrent( display, win, context );

Window は GLXDrawable です。
これでレンダリングできますが、OpenGL 2 以降の API を用いるには
個別に glXGetProcAddress() して関数を用意する必要があります。


(5) OpenGL API の取得

ゼロから始めるとここが一番手間かもしれません。
ただしこの流れは Windows で OpenGL を用いる場合と完全に同一です。
API 名 Table の作成、API の取り出しコードは Windows と共有することができます。

OpenGL のはじめ方 (Windows)

下記は API Table の例です。
OpenGL 4.x の glcorearb.h からスクリプトで自動変換して生成しています。
この方法だと、新しい API や extension 対応ドライバがリリースされたら
即座に対応することができます。

// flatlib3 GL_HeaderParser.py
// linux/GLFunction_GL4.inc

void (APIENTRY *p_glBlendColor)( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha );
void (APIENTRY *p_glBlendEquation)( GLenum mode );
void (APIENTRY *p_glDrawRangeElements)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices );
〜
void (APIENTRY *p_glTexPageCommitmentARB)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean resident );
// total=525


ImportListType ImportList_GL4[]= {
{ "glBlendColor", (void*)&p_glBlendColor },
{ "glBlendEquation", (void*)&p_glBlendEquation },
{ "glDrawRangeElements", (void*)&p_glDrawRangeElements },
{ "glTexImage3D", (void*)&p_glTexImage3D },
{ "glTexSubImage3D", (void*)&p_glTexSubImage3D },
〜
{ "glGetNamedStringARB", (void*)&p_glGetNamedStringARB },
{ "glGetNamedStringivARB", (void*)&p_glGetNamedStringivARB },
{ "glTexPageCommitmentARB", (void*)&p_glTexPageCommitmentARB },
{ NULL, NULL }

};
// total=525


// flatlib3 GL_HeaderParser.py
// linux/GLFunction_GL4.h

extern void (APIENTRY *p_glBlendColor)( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha );
inline void glBlendColor( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha )
{
    FL_GLAPI_ASSERT( p_glBlendColor );
    return p_glBlendColor( red, green, blue, alpha );
}
extern void (APIENTRY *p_glBlendEquation)( GLenum mode );
inline void glBlendEquation( GLenum mode )
{
    FL_GLAPI_ASSERT( p_glBlendEquation );
    return p_glBlendEquation( mode );
}
extern void (APIENTRY *p_glDrawRangeElements)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices );
inline void glDrawRangeElements( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices )
{
    FL_GLAPI_ASSERT( p_glDrawRangeElements );
    return p_glDrawRangeElements( mode, start, end, count, type, indices );
}
〜

Platform Interface のまとめ

Windows         WGL         GL4.x   wglGetProcAddress()
Linux X11       GLX         GL4.x   glXGetProcAddress()
OSX             NSOpenGL    GL4.1
OSX + X11       GLX         GL2.1   (XQuartz + HD4000)
Linux X11 + ES  EGL         ES2/3
Android         EGL         ES2/3
iOS             GLK/EAGL    ES2/3

Windows/Linux X11 は GetProcAddress() が必要ですが対応ドライバがあれば
新しい API をすぐ使えます。
OSX はそのまま OpenGL を使えて手間いらずですが 10.9 は OpenGL 4.1 まで。
OSX + XQuartz (HD4000) は残念ながら 2.1 でした。

Nexus 7 (2012) + Ubuntu desktop のように OpenGL ES 2.0 対応 GPU の
Linux では GLX ではなく X11 上で EGL + OpenGL ES 2.0 API が使えます。

Raspberry Pi や 旧 NetWalker のように、X が動いても OpenGL 未対応の場合
フレームバッファに直接レンダリングすることになります。
この場合も EGL なので、Window の有無にかかわらず利用できる EGL は互換性が
取りやすくなっています。


関連エントリ
Mac OS X 10.9 Mavericks の OpenGL 4.1 対応と Windows 8.1 Intel HD 4000 Driver
Mac OS X で OpenGL の描画 (Xcode5/retina)
Nexus 7 (2012) Ubuntu desktop 上での開発とバッテリー設定
NetWalker PC-Z1 i.MX515 OpenGL ES 2.0 (3)
OpenGL のはじめ方