OpenGLES 2.0 頂点フォーマットの管理

OpenGLES2.0 上にほぼ Direct3D 互換の環境を作っています。
頂点形式も同じものです。頂点バッファを作ることも出来ました。

GLuint _flFASTCALL
CreateBuffer(
    unsigned int bytesize,
    GLenum buffer,
    GLenum btype,
    const void* initdata
    )
{
    GLuint   bid;
    glGenBuffers( 1, &bid );
    glBindBuffer( buffer, bid );

    if( initdata ){
        glBufferData( buffer, bytesize, initdata, btype );
        ErrorHandle( "CreateBuffer" );
    }
    return  bid;
}

API 的に VertexBuffer / IndexBuffer の区別が無いのは Direct3D10 以降と同じです。

// VertexBuffer
vbuf= CreateBuffer( bytesize, GL_ARRAY_BUFFER, GL_STATIC_DRAW, vdata );

// IndexBuffer
ibuf= CreateBuffer( bytesize, GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, idata );

GL_STATIC_DRAW は Direct3D の D3D10_USAGE_/D3D11_USAGE_ や AccessFlag
に相当するものです。

glBIndBuffer( GL_ARRAY_BUFFER, vbuf );   // IASetVertexBuffers()
glBIndBuffer( GL_ELEMENT_ARRAY_BUFFER, ibuf );   // IASetIndexBuffer()

与えられるバッファの区別がないので、描画に使えるバッファは 1つだけ。
頂点データを同じバッファに格納することが前提となっているようです。

シェーダーとのバインドは glVertexAttribPointer() そのままです。

GLint loc_position= glGetAttribLocation( ShaderProgram, "POSITION" );
GLint loc_normal=   glGetAttribLocation( ShaderProgram, "NORMAL" );
GLint loc_texcoord= glGetAttribLocation( ShaderProgram, "TEXCOORD" );
glVertexAttribPointer( loc_position, 3, GL_FLOAT, GL_FALSE, 32, (void*)0 );
glVertexAttribPointer( loc_normal,   3, GL_FLOAT, GL_FALSE, 32, (void*)(sizeof(float)*3) );
glVertexAttribPointer( loc_texcoord, 2, GL_FLOAT, GL_FALSE, 32, (void*)(sizeof(float)*6) );

必要なパラメータもやってることも、D3D10_INPUT_ELEMENT_DESC,
D3D11_INPUT_ELEMENT_DESC によく似ています。
違うのは、バインドシンボルが SEMANTIC ではなく変数名そのままになること。

上の例だとシェーダー側はこんな感じです。
SEMANTIC を直接変数名にすることで D3D と同じような記述になります。

// GLSL
attribute vec3 POSITION;
attribute vec3 NORMAL;
attribute vec2 TEXCOORD;

void main()
{
   gl_Position= vec4( POSITION.xyz, 1.0 ) * Matrix;
}

Direct3D で書くと下記の通りです。

// HLSL
struct VS_INPUT {
   float3  vposition : POSITION;
   float3  vnormal   : NORMAL;
   float2  vtexcoord : TEXCOORD0;
};

float4 main( VS_INPUT vin ) : SV_Position
{
   return  mul( float4( vin.vposition.xyz, 1.0f ), Matrix );
}

デモやサンプルプログラムならこのままでいいのですが、汎用的な仕組みとして
シェーダーとのバインドを考えると、D3D の InputLayout 相当の機能が必要に
なることがわかります。(ID3D10InputLayout / ID3D11InputLayout)

なので実際に作っておきます。

// GLInputLayout.h
enum {
    ELEMENTFLAG_NORMALIZE  =  (1<<0),
    ELEMENTFLAG_ACTIVE     =  (1<<1),
    ELEMENTFLAG_END        =  (1<<2),
};

struct ShaderInputElement {
    unsigned int    Location;
    unsigned int    Type;       // GLenum GL_FLOAT
    unsigned char   Stride;
    unsigned char   ByteOffset;
    unsigned char   Components;
    unsigned char   Flag;
};
// GLInputLayout.cpp
void _flFASTCALL
CreateInputLayout( 
      ShaderInputElement* edesc,
      unsigned int desccount,
      GLuint shader,
      const a5::InputLayout& layout
        )
{
    unsigned int   ecount= layout.GetElementCount();
    assert( ecount <= desccount );
    for( unsigned int ei= 0 ; ei< ecount ; ei++, edesc++ ){
        const a5::InputElement*	ep= layout.GetElement( ei );
        unsigned int	component= 0;
        unsigned int	normal= 0;
        edesc->Flag= 0;
        if( ei == ecount-1 ){
            edesc->Flag|= ELEMENTFLAG_END;
        }
        GLint  loc= glGetAttribLocation( shader, ep->SemanticName );
        ErrorHandle( "Layout" );
        if( loc < 0 ){
            continue;
        }
        edesc->Location= loc;
        edesc->Type= PixelToFormat( ep->pixf, &component, &normal );
        edesc->Stride= static_cast( layout.GetStrideSize() );
        edesc->Components= static_cast( component );
        edesc->ByteOffset= static_cast( ep->ByteOffset );
        if( normal ){
            edesc->Flag|= ELEMENTFLAG_NORMALIZE;
        }
        edesc->Flag|= ELEMENTFLAG_ACTIVE;
    }
}

void _flFASTCALL
IASetInputLayout( const ShaderInputElement* edesc )
{
    const ShaderInputElement*   ep= edesc;
    for(;; ep++ ){
        if( ep->Flag & ELEMENTFLAG_ACTIVE ){ 
            glVertexAttribPointer(
                ep->Location,
                ep->Components,
                ep->Type,
                (ep->Flag & ELEMENTFLAG_NORMALIZE) ? GL_TRUE : GL_FALSE,
                ep->Stride,
                (const void*)ep->ByteOffset
                );
            glEnableVertexAttribArray( ep->Location );
        }
        if( ep->Flag & ELEMENTFLAG_END ){
            break;
        }
    }
}

void _flFASTCALL
IAResetInputLayout( const ShaderInputElement* edesc )
{
    const ShaderInputElement*	ep= edesc;
    for(;; ep++ ){
        if( ep->Flag & ELEMENTFLAG_ACTIVE ){ 
            glDisableVertexAttribArray( ep->Location );
        }
        if( ep->Flag & ELEMENTFLAG_END ){
            break;
        }
    }
}

CreateInputLayout() はオブジェクトを生成しないで固定バッファを使用します。
ShaderInputElement がそのまま描画時に使える InputLayout 情報になります。

複雑な Flag 管理を行っているのは、エンドマークやバッファ数を別に持たなくて
済むようにです。必要 Element 分、固定で渡されたバッファで完結させるため。
メモリアロケートに抵抗がなければオブジェクトにした方がずっとシンプルでしょう。
ShaderInputElement の Type が 32bit なのは GLenum の互換性を考えたためです。
実際のシンボル値は 16bit なので、問題なければ圧縮して total 8byte に収まります。
シェーダーに対応するシンボルが無ければ頂点のセットアップを行いません。

a5::InputLayout は、個人的に Direct3D で使用している頂点情報管理 object です。
本来はこの object を元に D3D11_INPUT_ELEMENT_DESC を生成して、
ID3D11InputLayout を作っていました。

ELEMENTFLAG_NORMALIZE の定義値が 1 なのは念のため。

PixelToFormat() は頂点フォーマットに含まれている Direct3D の
DXGI_FORMAT を OpenGL 形式に変換しています。詳細は次回以降で。

// 初期化
a5::InputLayout layout( ~ );
ShaderInputElement vLayout[3];
CreateInputLayout( vLayout, 3, ShaderProgram, layout );

// 描画時
glBIndBuffer( GL_ARRAY_BUFFER, vbuf );   // IASetVertexBuffers()
IASetInputLayout( vLayout );
glBIndBuffer( GL_ELEMENT_ARRAY_BUFFER, ibuf );   // IASetIndexBuffer()

glDrawElements( GL_TRIANGLES, Indices, GL_UNSIGNED_SHORT, NULL );

頂点バッファ (頂点バッファオブジェクト)を使った描画命令も、頂点の設定同様
これまでと同じものでした。
Index の配列はすでに GL_ELEMENT_ARRAY_BUFFER で渡しているため
glDrawElements() の最後は NULL になります。
Indices は Index の個数なので GL_TRIANGLES だと常に 3 の倍数となります。

描画の仕組みを理解して API を使うだけなら難しいことは特にないです。
でも実際にゲーム開発を行っていると、サンプルだけではわからない事項がいくつも
出てきます。3D の描画エンジンを作っていて常に悩まされるのは、
汎用的に使える仕組みとその設計部分です。

例えば CG ツールからどうやって export してどんなフォーマットで保存するか、
数多く存在する頂点フォーマットやピクセルフォーマットに対応するにはどうするか、
データとシェーダーの対応付けやシェーダーの管理をどうやって行うか、など。

3D 系プログラムの開発はこのような地味で面倒なことの方が多いです。
blog の冒頭説明でも書いてますが、ゲーム開発のノウハウの差というのは目新しい
技術の実装だけでなく、データ制作やデータの管理もかなり含まれているはずです。

とりあえず export したモデルが表示できて、任意のシェーダーを適用して
アニメーション出来るところまでは作成済み。

関連エントリ
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理

WiMAX ルータ URoad-5000 (4) 充電の仕方

URoad-5000 の使い方でわからない点があったのでいくつか問い合わせてみました。
以下その結果をまとめたものです。

●バッテリー LED の意味

モードは 2種類あります。Battery モード と Charge モード

「Batteryモード」(バッテリー利用時のインジケータの色の意味)

 緑: 60%~100%
 橙: 60%以下
 赤: 残量小
 赤点滅: 残量ほぼゼロ


「Chargeモード」 (AC アダプタ接続時のインジケータの色の意味)

 緑: 90%以上充電済み
 橙: 充電中

※ モードの切り替わり時は LED が 3回点滅する

AC アダプタを接続すると LED の色の意味が変わります。
たとえば残量 70% だったら、AC アダプタをつなぐと緑から橙へ。
モードの切り替わりを示しているのが LED の 3回点滅だそうです。

●バッテリー充電時間と稼働時間

  バッテリーLED          充電時間          稼働時間の目安
  ---------------------------------------------------------
 赤点滅→緑 (0%→95%)   2時間半~3時間    約3時間
 緑→緑 (95%→100%)     +30分             +15分

Chargeモードで 緑色まで充電すると、スペック通りおよそ 3時間利用可能。
緑まで充電してからさらに 30分充電すると 100% です。

●ACアダプタをつなぐと勝手に電源が入る点について

・AC アダプタをつなぐと電源が入るのは仕様。
 このときルーターの動作が不要なら、電源を切っておいても問題無いそうです。
 電源を入れなくても充電できます。

・AC アダプタ接続中はバッテリーを消費しません。
 本体の動作は、バッテリーを経由しないで直接 AC アダプタの電源を使っている
 そうです。電源を切っても消費電力は減るけど充電時間は変わりません。

●その他 URoad-5000 利用状況など

電車内など移動中の利用はあきらめて外出時に使える回線として活用しています。
昼食時に DS を Wi-Fi につないだりとか。
ポケットに入れられるくらい小さくて薄くて軽いことと、バッテリーで動作出来るのが
良いです。ほとんど負担にならずに持ち歩けます。

つるつるな上面部は結構指紋が目立つ感じです。専用ケースも付属しているのですが
サイズが増すので今は使っていません。

本体の LED はそうでもないのですが、USB アダプタ側 UD01SS の青色 LED は非常に
まぶしく感じます。電波を探している間は点灯したままなので特に。
夜間とか外で使ってると目立つかもしれません。暗くするか消せると良いのですが。

関連エントリ
WiMAX ルータ URoad-5000 (3) 三日目
WiMAX ルータ URoad-5000 (2) 二日目
WiMAX ルータ URoad-5000
Windows7 beta で UQ WiMAX

メビウス PC-NJ70A (9) まとめ

モニターももうすぐ三ヶ月です。
このクラスの同スペックな PC はたくさんあります。
基本的な PC 部分の性能はほぼ横並びでそれほど大きな違いはありません。
PC 部分はおそらくあまり他と変わらない感想になってしまうことでしょう。

だからこそ差別化が必要で、その点メビウスは強烈な個性を持っていました。
光センサー液晶パッドを搭載していることです。

タッチパッド部分が液晶画面なのです。
この液晶パネルがセンサーも兼ねているとのこと。
この不思議なデバイスは、各画素が光に反応し2次元のイメージとして取り込むことが
できるようです。
処理次第では全く制限のない入力装置になるかもしれません。

惜しむらくはこの個性とテクノロジーが、メビウス自体の魅力に思ったより
つながっていなかったということです。
この点過去の書き込みでいくつか考察してきました。

それでもレポート開始当初はかなり楽観視していました。
6月に開発キットが公開されるとの記事があったからです。

ASCII.jp 謎のMebiusは光センサー液晶パッド搭載のNetbook!
 >光センサー液晶パッドのアプリケーション開発に必要な情報や開発キットは、
 >同社から6月くらいに提供される予定。ユーザーにより開発された独自アプリ
 >ケーションの登場も期待される。

初物で使いづらい点があったとしても、もしかしたら自力で何とか出来るのでは無かろうかと。
レポートに書くネタにもなるし仕組みや出来ることに興味もあったからです。

でも結局開発キットは無し。
アプリケーションが一つ追加されたのみで、光センサー液晶パッドで出来ることは
残念ながら発売直後とあまり変わりませんでした。

●おすすめできる点

・手書き文字入力

パソコンで手書文字認識できる機能はよくあります。その扱いやすさを考えると
電子辞書のように手前に液晶パッドが付いているスタイルはたいへん理にかなっています。

タッチパネル付きの PC は少なくないですし、タブレット PC だって各種あります。
でもキーボードの奥の液晶画面まで手を伸ばして、そのままペンで書くのはたいへんです。
逆に液晶を折りたたんでタブレット形状にしてしまうとキーボード操作に難儀します。

メビウス PC-NJ70A は普段の PC スタイルながら、すぐ手が届く手前にタッチパッド
があり、いつでもペンを扱えます。キーボード操作も妨げません。
メイン液晶+キーボード+液晶パッド、といったスタイルは今のところメビウス
独自のものです。

もう少し動作が速くて気持ちよく操作できたなら、手書き以外の他の機能も
お勧めできたかもしれません。

●改良希望点

通常のタッチパッドと比べてまだ使いづらいところがあります。
独自の専用アプリケーションもまだまだ不足しているように思います。
このように 1つ 1つの細かい点は気になりますし、実際にレポートやアンケートで
報告させていただきました。
でも本当は、ソフトウエアで継続して更新していけるのが PC の良いところです。

結論として、今最も感じている足りないところは、発売&モニターレポート開始から
3ヶ月経過してもほとんど変わってないところだと思います。

光センサー液晶パッドを使ったアプリが次々と出ていたり、アイデアをすぐに実現
出来る環境があったなら、今は多少不満があっても
「今後なんだか良くなっていきそうだ」 とか 「今後面白くなっていきそうだ」 といった
勢いや流れが見えていたかもしれません。
自分で何とか出来そうだ、と思えるだけでも十分だったと思います。

おそらく今一番危惧しなければならないのは、このまま変わらないのではないかと
思ってしまうことでしょう。

関連エントリ
メビウス PC-NJ70A (8) タッチアプリとイルミスキャン
メビウス PC-NJ70A (7) 一ヶ月後
メビウス PC-NJ70A (6) ユーザーインターフェース
メビウス PC-NJ70A (5) サブモニタ
メビウス PC-NJ70A (4) 使用感
メビウス PC-NJ70A (3) Windows7 RC を入れる
メビウス PC-NJ70A (2) 初期セットアップとメモリ増設
メビウス PC-NJ70A 液晶センサータッチパッド
メビウス PC-NJ70A 画面解像度とタッチ

MacOSX の環境設定メモ

今まで全く使っていなかったので、やはりいろいろな面でつまずいたりします。
特に操作に慣れておらず、初歩的なキーボードやマウスの設定が落ち着くまでも
結構かかりました。以下そのメモです。

●キーボードの配列

Windows 用キーボードを USB 接続で使っています。
システム環境設定のキーボードショートカットから Ctrl キーの配置変更ができます。
でも ESC キーの入れ替えがありません。
ソフトウエアでの入れ替えだとアプリケーションによってキーボードの認識に差が
生じる可能性もあったのであきらめました。

結局ハード的にキーの入れ替え機能を持ったキーボード (DHARMAPOINT DRTCKB91UBK)
に交換して、キーボード側で Ctrl/ESC の位置を置き換えています。
Mac 標準の日本語キーボードだと最初から Control キーが A の左に配置されて
いるので、これで良かったのかもしれません。

●日本語変換のキーカスタマイズ

標準の日本語変換だと設定項目が少なくキー操作の変更ができませんでした。
Windows の場合、MS-IME でも変換操作のキーを自由に変更することができます。
たとえば日本語入力中の文節移動や文節伸縮の操作を全部ホームポジションから
操作できるように設定できます。

ATOK2009 を導入しました。
web にはこの辺の情報が無くカスタマイズができるかどうかわからなかったのですが、
Windows 版では出来ているので入れてみました。
結果当たりでした。Windows 版同様にキー操作を変更できます。
カーソル移動や文節伸縮などを任意のキーに割り当て使っています。

●マウスの挙動

最初は気にならなかったのですが、使っていてどうもマウス挙動に違和感を感じる
ようになりました。カーソルが移動が遅いので、速度設定をあげると微調整が
スムーズにできなくなります。小さいボタンがうまく押せない感じ。
当初マウスの問題かと思って、新しいマウスに取り替えたり電池を替えたり
マウスパッドを用意してみたり、ワイヤードマウスにしたりといろいろ試しました。
下記のページによると OS 側の問題だったようです。

MacOS Xのマウス挙動 Pert2 (absurd-wings)
Mac OS X のマウス加速問題

Microsoft 製のマウス (Explorer mini) と、マウス付属のユーティリティソフト
Microsoft IntelliPoint を導入することで使いやすくなりました。

OpenCL の vector 型

OpenCL で扱える SIMD の vector 型は、float4 や int4 といった型名表記に
なっています。この点は HLSL と同じです。
GLSL では vec4 や ivec4 等の独自の型が用いられていました。

また GLSL の場合 1コンポーネントの vec1 型は無く vec2~vec4 のみ。
OpenCL の仕様をよく見ると、最大 16要素まで扱えるものの使える組み合わせは
2, 4, 8, 16 だけです。つまり float1 も float3 もありません。

GLSL       (float)  vec2     vec3     vec4
OpenCL     (float)  float2   -        float4   float8   float16
HLSL       float1   float2   float3   float4

Compute Shader で用いられる HLSL では float1~float4 すべて使えます。
もともと vector<> の別名にすぎず、1~4 まで任意の数値を与えることができます。
つまり下記の定義と同じです。

typedef vector float4;

また float v; と宣言した場合の v.x が許されるのも HLSL 独自かもしれません。

OpenCL は要素数が増えているため、swizzle も xyzw だけでなく s 表記が
追加されています。(rgba は無し)

float16  v;
  v.s0 // == v.x
  v.s1 // == v.y
  v.s2 // == v.z
  v.s3 // == v.w
  v.s4
  ~
  v.se
  v.sf

例えば v.xy は v.s01 と記述可能。float16 の全要素代入を明示的に書くなら

float16  d, s;
d.s0123456789abcdef = s.s0123456789abcdef;

逆順にするなら

d.s0123456789abcdef = s.sfedcba9876543210;

float16 を 4×4 matrix とみなすなら Transpose (転置) は

d.s0123456789abcdef = s.s048c159d26ae37bf;

と書けます。
値数は括弧表記で、コンストラクタではなくキャストが付きます。

float16  d= (float16)(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

すべて同じ値ならスカラーからの変換が使えて d= (float16)(0); と書けます。
スカラー以外は個数が一致する必要があります。

float4	 a= (float4)( 1.0f );
float16  d= (float16)( a, a, a, a );
float16  e= (float16)( (float8)(1.0f), (float8)(2.0f) );

一つ下のサイズの vector を取り出す suffix もあります。odd, even, lo, hi

float16  a= (float16)(1);
float8   b= a.odd; //== a.s13579bdf
float4   c= b.odd; //== b.s1357
float2   d= c.odd; //== c.s13 == c.yw
float8   l= a.lo;  //== a.s01234567
float8   h= a.hi;  //== a.s89abcdef
float4   ll= a.lo.lo; //== (a.s01234567).s0123 == a.s0123

おそらく特定の swizzle の別名です。
s 表記でも書けます。a.s01234567.s1357.s01 (== a.lo.odd.lo)

opencl-1.0.43 には 4×4 Transpose の応用例が載っています。

KHRONOS GROUP OpenCL
Khronos OpenCL API Registry

opencl-1.0.43.pdf page 134

//transpose
t.even = x.lo;
t.odd = x.hi;
x.even = t.lo;
x.odd = t.hi;

x t はどちらも float16。
展開してみます。

t.s02468ace = x.s01234567;
t.s13579bdf = x.s89abcdef;
// t: 0 1 2 3 4 5 6 7    <= x.lo
//     8 9 a b c d e f   <= x.hi
// t= 08192a3b4c5d6e7f

x.s02468ace = t.s01234567;
x.s13579bdf = t.s89abcdef;
// x: 0 8 1 9 2 a 3 b    <= t.lo
//     4 c 5 d 6 e 7 f   <= t.hi
// x= 048c159d26ae37bf

なんだか SSE のプログラムを見ているようです。
1行で記述できるのにこのような例が載っているということは、
Larrabee のプログラミングはこんな感じなのかもしれません。

GPU べったりのシェーダーとはまた違った印象です。

関連エントリ
ATI Stream SDK 2.0 beta と OpenCL