Archives

August 2009 の記事

OpenGL ES 2.0 は必要な機能のみ用意されているため非常にわかりやすくなっています。
レガシーだけど互換性のために残っている API が、ほとんど無いからです。
定期的に仕様をゼロから作り直す DirectX に似ているといえるかもしれません。

複数の描画方法が残っていると、どれを使えばよいのかわからなくなることがあります。
OpenGLES 2.0 では悩むことが少なくて済みました。
モバイル向けサブセットであり仕様を割り切っていること、ShaderModel 3.0 相当の
シェーダーが利用可能なことから、OpenGL の入門としても参考になりました。

PC 上でも OpenGL ES 2.0 Emulator を使えば全く同じ API を使うことが出来ます。

AMD Developer Central GPU Tools
Imagination PowerVR Insider

この両者、初期化がわずかに異なるだけで dll 名も一緒。全く同じように使えるようです。
コードを共有化する場合、NativeWindowType でどちらを使っているか判断できます。

#include  <EGL/egl.h>
#include  <GLES2/gl2.h>
#include  <GLES2/gl2ext.h>

#ifdef	NativeWindowType
# define  USE_GLES20_AMD   1
#else
# define  USE_GLES20_PVR   1
# include <GLES2/gl2extimg.h>
#endif

初期化の違いもこれだけです。

#if USE_GLES20_AMD
static const EGLint attrib[]= {
    EGL_RED_SIZE,       5,
    EGL_GREEN_SIZE,     6,
    EGL_BLUE_SIZE,      5,
    EGL_ALPHA_SIZE,     0,
    EGL_DEPTH_SIZE,     16,
    EGL_STENCIL_SIZE,   0,
    EGL_SAMPLES,        4,
    EGL_NONE
};
egl_Display= eglGetDisplay( EGL_DEFAULT_DISPLAY );
#else // PVR
static const EGLint attrib[]= {
    EGL_LEVEL,                  0,
    EGL_SURFACE_TYPE,           EGL_WINDOW_BIT,
    EGL_RENDERABLE_TYPE,        EGL_OPENGL_ES2_BIT,
    EGL_NATIVE_RENDERABLE,      EGL_FALSE,
    EGL_DEPTH_SIZE,             EGL_DONT_CARE,
    EGL_NONE
};
hDC= GetDC( hWnd );
egl_Display= eglGetDisplay( hDC );
#endif

EGLint  majorv= 0;
EGLint  minorv= 0;
eglInitialize( egl_Display, &majorv, &minorv );

EGLint	configs= 0;
eglGetConfigs( egl_Display, NULL, 0, &configs );
eglChooseConfig( egl_Display, attrib, &egl_Config, 1, &configs );
egl_Surface= eglCreateWindowSurface( egl_Display, egl_Config, hWnd, NULL );
static const EGLint   attrlist[]= {
    EGL_CONTEXT_CLIENT_VERSION,
    2,
    EGL_NONE
};
egl_Context= eglCreateContext( egl_Display, egl_Config, EGL_NO_CONTEXT, attrlist );
eglMakeCurrent( egl_Display, egl_Surface, egl_Surface, egl_Context );

PC では本来の OpenGL が使えるので、3D を描画する目的でわざわざ Emulator を使う
必要はないです。元々 GL ES 自体がサブセットであり OpenGL でほぼ同じ API が使えます。
今回いろいろ試しているのも手持ちの描画エンジンを様々なプラットフォームで
そのまま動くようにしているから。

GLSL に mat4x3 が無いとかバイナリ形式の扱いとか多少機能不足もありますが
その辺は何とでもなります。Direct3D Mobile がなかなか更新されないので、
Mobile 向け API は GL ES 2.0 で統一してくれた方が良いですね。

Direct3D11 (DirectX11) に慣れすぎたせいで、ついリソース生成をサブスレッドで
実行してしまいます。リソースの生成は描画と同じメインスレッドで無いと失敗します。


関連エントリ
OpenGL ES 2.0
OpenGLES2.0 DDS テクスチャを読み込む
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理


2009/08/29
OpenGL ES 2.0

MID SmartQ5 が気になったので、3D 描画性能などを調べていたら下記のページに
いくつかヒントがあることに気がつきました。

Khronos Conformant Products

Khronos のトップより右上の View all Conformatn Products。
OpenGL ES 2.0 に対応しているグラフィックスコアがだいたいわかります。
2.0 に対応しているのはこのあたり。

・Imagination Technologies PowerVR SGX 535
・Samsung Electronics FIMG-3DSE v1.5
・ARM Mali-400 MP
・Imagination Technologies PowerVR SGX 520
・NVIDIA Corporation Tegra
・Vivante Corporation
・ARM Mali 200
・Imagination Technologies PowerVR SGX530
・Advanced Micro Devices AMD Z430?

iPhone が PowerVR SGX 535 であることもわかります。
下の方には Cell の記載も。JETSTREAM-A は Cell を使ったレンダラでしょうか。

PowerVR SGX は Unified Shader で GPGPU や ShaderModel4.0 (DirectX10)
も見据えた設計になってますが、Mali 400 等は Vertex/Pixel が独立した
ShaderModel 3.0 系のデザインとなってるようです。

ARM社がグラフィックス描画処理コア「Mali-400 MP」を発表,画素処理部の数を可変に

Wikipedia NVIDIA Tegra によると Tegra も GeForce6 系らしいのでおそらく
ShaderModel 3.0 でしょう。
GLES 2.0 自体 3.0 世代 (DirectX9) なので十分ですが、ハード機能的には世代が
混在している状態です。実際の速度は機能よりシェーダーユニットの個数で決まるため
判断は難しくなっています。

GLES 2.0 世代はおおざっぱに 14M~35M triangle/sec, 275M~1G pixel/sec あたりと
仮定すると 275MHz x Pixel Units または 20cycle/vertex くらいの計算でしょうか。

Freescale i.MX31 は PowerVR MBX Lite (Wikipedia PowerVR) が使われていました。
i.MX51 は GLES 2.0 対応なので別物です。どうやら AMD Z430 らしい。
(Google i.mx51+z430)


Toshiba T-01A の d3dmcaps を調べてもらいました。
wm_gamer さん協力ありがとうございました。

Direct3D Mobile DeviceCaps 一覧

Driver="YAMATO_D3DM", Description="AMD Yamato D3D Mobile Driver"
   yes D3DMDEVCAPS_HWTRANSFORMANDLIGHT
   yes D3DMDEVCAPS_HWRASTERIZATION

上記の通り 3D アクセラレータが有効となっています。AMD (ATI) core です。
ドライバはこれまで見たことがない YAMATO_D3DM というもの。

T-01A は Qualcomm の Snapdragon を搭載しています。
Snapdragon は Cortex-A8 同様、新しい ARMv7 世代の ARM core を採用しており、
さらに 1GHz で動作するのが特徴です。

Impress Qualcommの携帯電話向けプロセッサ「Scorpion」~独自実装で1GHz駆動を実現

上の記事によると、かつての StrongARM のように独自の実装であることがわかります。
Cortex-A8 と同じくアウトオブオーダーのスーパースカラで、レジスタリネーミングなど
より積極的な改良が行われているようです。
嬉しいのは VFP 対応なこと。WindowsMobile も、iPhone のように当たり前に
浮動小数演算を使えるようになって欲しいところです。

3D 性能も強化されており、上の d3dmcaps を見ても機能の対応度がこれまでの
MSM7201A (Touch Diamond 他) とは全然違います。

OpenGLES2.0 対応とのことなので、ハードウエア的にはシェーダー世代のはず。
Direct3D8 相当で固定パイプしかない Direct3D Mobile (D3DM) は手狭なのでしょう。
現状で性能を出し切るにはおそらく OpenGLES 2.0 が必要です。

Mobile の世界では Direct3D , OpenGL の立場が逆転しており、Direct3D Mobile の
仕様はかなり遅れているといわざるを得ません。

d3dmcpas のリストを見て特筆すべき点は、出力対応フレームバッファとして 32bit
D3DMFMT_A8R8G8B8 が列挙されていることです。
WindowsMobile は 16bit カラーしか対応しておらず、これまでも最大発色数は 65536色 の
ままでした。当たり前のようにフルカラー対応機種が出ている携帯電話と比べると、だいぶ見劣り
していたことになります。

T-01A / Snapdragon がフルスクリーン時のフルカラー出力に対応しているのだとしたら
私が知る限りでは初だと思います。


関連エントリ
HTC Touch Diamond で Direct3DMobile その(10) d3dmclock v1.10 3Dクロック 更新
HTC S11HT の 3dmcaps


NVIDIA の新しいドライバ 190.62 がコンピュートシェーダに対応したらしいので試しました。
Windows7 RC/RTM どちらでも Compute Shader 4.0 動いてます。

(Direct3D11)
*HARDWARE  = 10.0 (a000)
 feature threading  DriverConcurrentCreates=1
 feature threading  DriverCommandLists=0
 feature d3d10_x  ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=1
 feature doubles  DoublePrecisionFloatShaderOps=0


関連エントリ
Direct3D11/DirectX11 GeForce の ComputeShader とドライバ
Direct3D11/DirectX11 ComputeShader 4.0 を使う
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる


ファントムダスト Phantomdust というゲームがありました。
今更ですが…忘れないうちにいくつか書いておきます。

マップの破壊表現は複数の手法の組み合わせで出来ています。
オブジェクト、パーティクル、破裂シェーダー、破壊のへこみ穴 等。
今回説明するのは「穴」の表現です。

攻撃やダメージ等によって、地形モデルのどの位置にでも削れたような穴があきます。
地面だけでなく、壁でも天井でも同様に破壊の痕跡が残ります。
このへこんだ形状は別モデルとして用意してあり、ステンシルマスクを用いて背景の
描画時に合成しています。
戦っているうちに地形の至る所が穴だらけになるわけです。

例えば下記リンク先のイレースシェルの地面がそうです。
いくつも穴がありますが、もともとは平らな地面のモデルデータです。

famitsu.com
Impress GAME Watch


●特徴

メリット

・デカールと違い立体的に見えます。
・背景モデルデータを書き換えることなく形状を変化出来ます。
・立体的なデータなのでヒット位置を厳密に割り出さなくても描画できます。
・ノーマルマップの背景に対して低コストで適用できます。

デメリット

実装時に様々な制限が生じます。問題をどのように解決したのかは後述します。
欠点というか実装できなかったのはコリジョンの追従です。コリジョンの変更はコストが
高く、ゲーム内容に関わるため通信同期も必要でした。残念ながら見送りました。


●この方法を用いた理由

破壊表現は当初からの課題でした。
プロトタイプの段階では通常のマテリアルを使っていたので、レイヤテクスチャ+
メッシュを分割+動的に頂点を書き換えていました。
問題となったのはノーマルマップです。
Xbox 1 の当時はまだ珍しかったのですが、最終的に背景もキャラも全面にノーマルマップを
使っています。これはハイポリのディテールを少ないポリゴンで再現可能な手法なので、
結果として頂点数を減らすように方針転換しました。よって

・頂点を動かすだけのポリゴンの細分化が出来ない
  頂点を減らせるのがメリットだし、TS の容量も計算も減らしたい
・変形した形状の Tangent Space の再計算はコストがかかる
・PixelShader の 8+4 stage をノーマルマップで使い切っている
  レイヤを増やすとマルチパスになる


●破壊穴モデルの形状

実際は平べったい形状ですが便宜上球と思って構いません。
穴の中のへこみを表現するモデルなので、面が裏返った内側を向いたデータです。

破壊の穴と地面の交差を判定するボリュームモデルと、実際に穴の中を描画する
描画モデルの 2つが必要となります。

・交差判定のためのボリュームモデル
・穴の中を描画するモデルデータ

描画モデルのマテリアルが単純かつ軽量なものだったので、この両者を兼用しています。
つまり描画モデルをそのまま断面の判定にも使用しています。


●断面の生成

シャドウボリュームと同じです。
テクスチャをフェッチしない専用の軽量シェーダーを割り当てて、ボリューム形状を
ステンシルバッファに 2回書きます。

(1) depth test かつ 表面、stencil +1
(2) depth test かつ 裏面、stencil -1

裏面と面面の差分が地面との交差面となります。上記の設定だと stencil が 0以外で
交差面です。
この条件を元に、同じ位置に描画モデルをレンダリングすれば合成ができます。

余談ですが、両面ステンシル機能がある GPU なら一度の描画で差分が求まります。
両面ステンシルが無くても両面マテリアルで代用可能で、例えばフレームバッファの
Alpha (DestAlpha) に加算で書き込むと一回で生成できます。
このとき描画頂点数は減りますがピクセル負荷は変わりません。

多くの GPU は Depth + Stencil の書き込みに特化したモードがあり、フィルレート
が倍になるように最適化されています。なので DestAlpha を使ってもあまりメリット
はありませんでした。
それにこのゲームは DestAlpha をすでに別の用途で利用していました。


●形状の変更

このゲームは表現上ステンシルバッファを多用しています。
8bit しかない stencil を bitmask して複数使い分けているところもあります。

・影
・オーラ
・地面や建物の破壊穴

もしゲーム画面か古い CGWORLD を見ることが出来るなら、穴の中にも形状に沿って影が
落ちていることがわかると思います。
でも描画しているモデルデータは平らなままです。

背景の影もシャドウボリュームを使っているのは、このように破壊によって動的に
地形が変わる可能性があるため。テクスチャや頂点への焼き込みだと、地形の変化に
対応できなくなります。


●負荷の相殺

破壊が進むと描画の負荷が増えて重くなりそうに見えますが実はそうでもありません。

このゲームは背景もキャラもすべてノーマルマップを使った重いシェーダーを適用
しています。最初から背景のピクセルが一番重い前提で負荷を計算しているので、
地形の描画面積を減らす処理は描画負荷の軽減につながります。

ノーマルマップを適用していない破壊の穴モデルは、数が増えれば増えるほど地形の
描画負荷を軽くすることになります。
ステンシルバッファへのレンダリングが増えますが相殺して軽くなります。
(でも頂点は軽くなりません。この判断は今思うと微妙だったかもしれません。)

同じようにキャラクタの描画負荷も相殺するのでアップになっても速度は変わりません。
半透明のエフェクトはまずいです。


●矛盾の解決

実際に組み込んでみると単純な表現でもさまざまな問題が生じます。
他の描画や表現と共存が必要だからで、1つ 1つ対策を施していきます。

・穴が開くと困る場所

破壊モデル合成時に、特殊な形状や半透明など矛盾が生じる場所があります。
あらかじめマテリアルの設定で描画しないように除外しています。
これは描画パスを分けることで実現しています。

・穴同士の重なり

Z test は別の判定に用いるので、描画の重なりの解決に Z バッファが使えません。
同じ位置にポリゴンが重なると矛盾が生じるので、描画位置を決める際に当たり判定を
行っています。必ず他の穴と重ならない位置に配置しています。

・地面の下だけ描画

破壊の穴形状は多少ずれても問題がないように球形をしています。
上半分が見えてしまわないように、地面の下に潜り込んだ部分だけ切り取るため
depth test を併用しています。Stencil がパスしてかつ、Z Fail が条件です。
地面の下にめり込んだ部分だけが描画されるようになります。

・depth 強制更新

ただ描画するだけでは不十分であることがわかります。
見た目と Z buffer に食い違いが生じるからです。
たとえば穴の上に置いた物や影が、地面の位置でまっすぐに切れてしまいます。

よって描画モデルを描き込むときに同時に depth を強制的に上書きします。
depth (Z buffer) が更新できれば、その後に描画する Shadow Volume も穴の形に
沿って落ちてくれます。

・遠くの穴が見えてしまう

地面が階層化している場所など、上の段の破壊穴と下の段の穴が重なって描画される
ことがあります。depth test の条件に Z Fail を設定しているということは、常に
遠くのオブジェクトが優先されることに等しいからです。
穴の中にさらに遠くの穴が見えてしまいます。

そこで穴モデル自体を Zソートして手前から描画し、同時にステンシルに 0 を書き
込んで消していきます。遠くの穴を描画する段階では depth test の Z Fail を
通っても、stencil テストに失敗するので描画されなくなります。

・地形との合成

上の問題を対処するためステンシルバッファを 0 で消してしまうと、今度はその
位置だけ地形を除外できなくなります。つまり穴の位置に上書きしてしまいます。
ステンシルをクリアする場合、別の条件で区別できる値を書き込まなければなりません。
元々ステンシル値の 0 または 0 以外で判定していたので思ったより簡単ではないです。
もしくは描画順番で何とかします。交差面を求めた後、地形の穴の中を描画する前に
先に地面を描画し、その後破壊穴モデルを描画すれば 0 クリアでも問題ありません。

・穴の上に重なるモデルの表示順番

地面に穴が開くはずの場所に他のモデルが置いてある場合も描画順番が問題となります。
例えばキャラが地面に立っていて、わずかに地面にめり込んでいる場合。(本当は
それが良くないのですが)
穴を開ける前の Z バッファで描画してしまうとめり込んだ部分が切れてしまいます。
だから地形に乗っているものは、穴を開けて Z バッファを更新したあとに描画する
ことになります。

・個数制限と消去

破壊の穴は同時に 30個描画しています。上限はただの定数値で、何らかの制限があった
わけではないです。これを超えた場合、視野に入っていないものから消しています。


●描画手順のまとめ

(1) 破壊可能部分の depth のみ
(2) 破壊穴モデルの交差判定
(3) 破壊穴の描画と depth 更新+識別可能な stencil で書き直す(要確認)
(4) 地形の上に乗っているモデル描画、不透明物一般
(5) 破壊不可能部分描画
(6) 破壊可能部分のカラー描画
(7) シャドウボリューム
(8) 影の合成
(9) 半透明地形の描画
(10) 半透明エフェクト

記憶だけで書いているので、おそらく異なっている部分や足りない部分があります。
これ以外にも特殊なエフェクトや表現があったので、実際はもっと描画手順が複雑です。
具体的に設定したステートの組み合わせなどは、おそらくもう一度実際に作ってみないと
わからないと思います。
それに今のハードならもっと簡単でしょう。ShaderModel 1.x の当時とは違うので、
別の表現方法も使えると思います。


昨日の続きです。ミップマップと 24bit テクスチャに対応します。
非圧縮形式はほぼ読み込めるようになりました。

DDS ファイルのミップマップの判定です。

    int	mipLevel= 1;
    if( (dp->dwCaps & DDSCAPS_MIPMAP)
            && (dp->dwFlags & DDSD_MIPMAPCOUNT)
            && dp->dwMipMapCount ){
        mipLevel= dp->dwMipMapCount;
    }

DDS テクスチャに含まれている Mipmap Level 数は dwMipMapCount に入ります。
本来最小値は 1 ですが、ツールによっては dwMipMapCount を設定していないものがあります。
dwMipMapCount が有効かどうかは dwFlags の DDSD_MIPMAPCOUNT で判別します。
DDSD_MIPMAPCOUNT が設定されていない場合、また念のため dwMipMapCount が 0 の
場合も 1 と見なした方が良いでしょう。

前回調べたように DDS と OpenGL では一部ピクセル形式に違いがあります。
MipLevel ごとに毎回変換するのは避けたいので、フォーマット判別時に全部書き換えてしまいます。
この変換処理を一度で済ませるために、まずはデータに含まれる全ピクセルの総数を求めます。

MipMap が無ければ width * height です。
何も考えずにファイルサイズ分全部変換してしまうのが一番楽かもしれません。
でも敢えて計算で求めてみました。

  unsigned int  bdif= IntLog2( IntMin( width, height ) ) << 1;
  unsigned long long  mbit= 0xaaaaaaaaLL| ((0x100000000LL >> bdif) - 1);
  mbit &= ~((0x100000000LL >> (mipLevel<<1))-1);
  pixcount= static_cast<unsigned int>( (width * height * mbit) >> 31 );

これで任意レベル数の mipmap の合計容量がわかります。
テクスチャが正方形なら (width * height * 0xaaaaaaaaLL)>>31 だけで済みます。
縦横のサイズが異なっている場合の補正が最初の 2行の bdif です。
1pixel を半分にした場合に 0.5pixel にならないよう最小値を clamp しています。

3行目はテクスチャ画像に含まれる MipLevel を制限するためのものです。
たとえば実際のゲームでも、ミップマップでテクスチャがぼけすぎるのを避けるために
MipLevel 数を制限することがあります。1024x1024 サイズでも 5 level 分しか格納
されていないことがあり得るわけです。

IntMin() は最小値を返しているだけ。
問題は IntLog2 の部分ですが x86/x64 では下記のように 1命令で記述することができます。

inline unsigned long IntLog2( unsigned int param )
{
    unsigned long  ret;
    _BitScanReverse( &ret, static_cast<unsigned long>( param ) );
    return  ret;
}

_BitScanReverse() は intrinsic で x86 の bsr 命令のことです。

他の CPU 対応を考えると、_BitScanReverse を置き換えるよりも最初からループ等で
MipMap の容量を求めた方が良いかもしれません。

#include  "TextureCache.h"
#include  "IntLog2.h"

#define	DDSCAPS_MIPMAP	    0x00400000
#define	DDSD_MIPMAPCOUNT    0x00020000

struct T_DDSHEADER {
    unsigned int    dwMagic;
    unsigned int    dwSize;
    unsigned int    dwFlags;
    unsigned int    dwHeight;
    unsigned int    dwWidth;
    unsigned int    dwPitchOrLinearSize;
    unsigned int    dwDepth;
    unsigned int    dwMipMapCount;
    unsigned int    dwReserved1[11];
    unsigned int    dwPfSize;
    unsigned int    dwPfFlags;
    unsigned int    dwFourCC;
    unsigned int    dwRGBBitCount;
    unsigned int    dwRBitMask;
    unsigned int    dwGBitMask;
    unsigned int    dwBBitMask;
    unsigned int    dwRGBAlphaBitMask;
    unsigned int    dwCaps;
    unsigned int    dwCaps2;
    unsigned int    dwReservedCaps[2];
    unsigned int    dwReserved2;
};
#define	__DDS__MAGIC	0x20534444	// ' SDD'

static inline unsigned int IntMin( unsigned int a, unsigned int b )
{
    return  a < b ? a : b;
}

template<unsigned int AShift, unsigned int AMask>
static void DDStoGL16( unsigned int count, void* data )
{
    unsigned short*	ptr= reinterpret_cast<unsigned short*>( data );
    for(; count-- ;){
        unsigned int	color= *ptr;
        unsigned int	alpha= color & AMask;
        *ptr++= (color << (16-AShift)) | (alpha >> AShift);
    }
}

static void DDStoGLByte( unsigned int count, void* data, unsigned int stride )
{
    unsigned char*	ptr= reinterpret_cast<unsigned char*>( data );
    for(; count-- ;){
        unsigned char   r= ptr[0];
        unsigned char   b= ptr[2];
        ptr[0]= b;
        ptr[2]= r;
        ptr+= stride;
    }
}

GLuint _flFASTCALL
TextureCache::DDSLoader( void* mem )
{
    const T_DDSHEADER*	dp= reinterpret_cast<T_DDSHEADER*>( mem );
    int     width= dp->dwWidth;
    int     height= dp->dwHeight;
    void*   data= reinterpret_cast<void*>( reinterpret_cast<intptr_t>( mem ) + sizeof(T_DDSHEADER) );

    GLenum  format= GL_NONE;
    GLenum  ftype= GL_UNSIGNED_BYTE;

    int	mipLevel= 1;
    unsigned int  pixcount= width * height;

    if( (dp->dwCaps & DDSCAPS_MIPMAP)
            && (dp->dwFlags & DDSD_MIPMAPCOUNT)
            && dp->dwMipMapCount ){
        mipLevel= dp->dwMipMapCount;

        unsigned int    bdif= IntLog2( IntMin( width, height ) ) << 1;
        unsigned long long  mbit= 0xaaaaaaaaLL| ((0x100000000LL >> bdif) - 1);
        mbit &= ~((0x200000000LL >> (mipLevel<<1))-1);
        pixcount= static_cast<unsigned int>( (pixcount * mbit) >> 31 );
    }

    switch( dp->dwRGBBitCount ){
    case 32: // 8888
        switch( dp->dwRBitMask ){
        case 0x000000ff: // R G B A = GL
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0x00ff0000: // B G R A = DX
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            DDStoGLByte( pixcount, data, 4 );
            break;
        }
        break;
    case 24: // 888
        switch( dp->dwRBitMask ){
        case 0x0000ff: // R G B = GL
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0xff0000: // B G R = DX
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            DDStoGLByte( pixcount, data, 3 );
            break;
        }
        break;
    case 16:
        switch( dp->dwGBitMask ){
        case 0x00f0: // 4444
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_4_4_4_4;
            DDStoGL16<12,0xf000>( pixcount, data );
            break;
        case 0x07e0: // 565
            format= GL_RGB;
            ftype= GL_UNSIGNED_SHORT_5_6_5;
            break;
        case 0x03e0: // 1555
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_5_5_5_1;
            DDStoGL16<15,0x8000>( pixcount, data );
            break;
        case 0x0000: // L A 88
            format= GL_LUMINANCE_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 8:
        if( dp->dwRGBAlphaBitMask ){
            format= GL_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
        }else{
            format= GL_LUMINANCE;
            ftype= GL_UNSIGNED_BYTE;
        }
        break;
    }

    if( format == GL_NONE ){
        return	0;
    }

    GLuint  texid= 0;

    glGenTextures( 1, &texid );
    glBindTexture( GL_TEXTURE_2D, texid );

    for( int mi= 0 ; mi < mipLevel ; mi++ ){
        glTexImage2D(
                GL_TEXTURE_2D,
                mi,     // level
                format, // internal format
                width,
                height,
                0,      // border
                format,
                ftype,
                data
                );
        data= reinterpret_cast<void*>( reinterpret_cast<intptr_t>( data ) + ((width * height * dp->dwRGBBitCount) >> 3) );
        width>>= 1;
        height>>= 1;
        if( width <= 0 ){
            width= 1;
        }
        if( height <= 0 ){
            height= 1;
        }
    }

    if( mipLevel > 1 ){
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    }else{
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    }
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    return  texid;
}

TextureCache は多重にテクスチャが読み込まれないよう参照管理を行っています。
リソースマネージャーは統合されており、テクスチャやシェーダー、スクリプトファイル
等が同じ仕組みで管理下に置かれています。


関連エントリ
OpenGLES2.0 DDS テクスチャを読み込む
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理


DXT/BC 等の圧縮や Mipmap/Cubemap, HDR 対応などを考えると DDS は便利な
テクスチャフォーマットです。OpenGLES でも読めるようにしておきます。
まずは非圧縮の 2D 形式のみ考えます。

とりあえず適当に…
これ を書いた当人と思えないくらい いい加減 な判定ですが、DDS (Direct3D) と OpenGL
のフォーマットの対応はこんな感じになります。
後で説明しますがこの段階ではまだ不完全です。

GLuint _flFASTCALL
TextureCache::DDSLoader( void* mem )
{
    const T_DDSHEADER*	dp= reinterpret_cast<T_DDSHEADER*>( mem );
    int     width= dp->dwWidth;
    int     height= dp->dwHeight;
    void*   data= reinterpret_cast<void*>( reinterpret_cast<intptr_t>( mem ) + sizeof(T_DDSHEADER) );

    GLenum  format= GL_NONE;
    GLenum  ftype= GL_UNSIGNED_BYTE;

    switch( dp->dwRGBBitCount ){
    case 32: // 8888
        switch( dp->dwRBitMask ){
        case 0x000000ff: // R G B A = GL
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0x00ff0000: // B G R A = DX
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 24: // 888
        switch( dp->dwRBitMask ){
        case 0x0000ff: // R G B = GL
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0xff0000: // B G R = DX
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 16:
        switch( dp->dwGBitMask ){
        case 0x00f0: // 4444
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_4_4_4_4;
            break;
        case 0x07e0: // 565
            format= GL_RGB;
            ftype= GL_UNSIGNED_SHORT_5_6_5;
            break;
        case 0x03e0: // 1555
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_5_5_5_1;
            break;
        case 0x0000: // L A 88
            format= GL_LUMINANCE_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 8:
        if( dp->dwRGBAlphaBitMask ){
            format= GL_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
        }else{
            format= GL_LUMINANCE;
            ftype= GL_UNSIGNED_BYTE;
        }
        break;
    }

    GLuint  texid= 0;
    glGenTextures( 1, &texid );
    glBindTexture( GL_TEXTURE_2D, texid );

    glTexImage2D(
            GL_TEXTURE_2D,
            0,      // level
            format, // internal format
            width,
            height,
            0,      // border
            format,
            ftype,
            data
            );

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    return  texid;
}

OpenGLES で対応できそうなのは下記の各フォーマットです。
A8R8G8B8, A8B8G8R8, R8G8B8, A4R4G4B4, R5G6B5, A1R5G5B5, A8L8, A8, L8
でも実際に表示してみると正しく出るのは一部だけ。
A8R8G8B8, R8G8B8, A4R4G4B4, A1R5G5B5 は色ずれがあり失敗します。

Direct3D と OpenGL ではピクセル内の配置が異なっているようです。
調べてみました。


● OpenGL の pixel 配列

原則として R G B A の順番となります。

各コンポーネント(RGB)が独立している場合は、メモリ上に R G B A の順で配置
されます。たとえば 32bit 8888 がそうです。Byte 単位で書き込まれています。
64bit, 128bit といった Float 形式も同様です。
この順番は x y z w の並びに一致するため、シェーダーから見ても自然なものです。

ただし 8888 を DWORD (32bit) で読み込んだ場合、リトルエンディアンでは下記の
bit 並びになります。最下位が R で最上位が Alpha です。

31 H<---------------->L 0
|  A  |  B  |  G  |  R  |    GL_RGBA + GL_UNSIGNED_BYTE  (R8 G8 B8 A8)

コンポーネントが UNSIGNED_SHORT にパックされている場合は下記の内容になります。
リトルエンディアンで読み込んだ 32bit 8888 と逆順です。

15 H<-------->L 0
| R | G | B | A |          GL_RGBA + GL_UNSIGNED_SHORT_4_4_4_4  (R4 G4 B4 A4)

15 H<-------->L 0
| R  | G  | B |A|          GL_RGBA + GL_UNSIGNED_SHORT_5_5_5_1  (R5 G5 B5 A1)

15 H<-------->L 0
| R  |  G  | B  |          GL_RGB + GL_UNSIGNED_SHORT_5_6_5  (R5 G6 B5)

OpenGL の 32bit 8888 形式は、GL_UNSIGNED_BYTE というシンボル通り Byte
アクセスを想定しています。


● Direct3D の pixel 配列 (32bit 8888 の場合)

・ Direct3D9 以前

Direct3D9 まではフォーマット名称の表記が OpenGL と逆順でした。Direct3D 上は
ARGB と表記されますが、OpenGL の呼び方にあわせると BGRA 相当です。
これは下記の bit 並びを見るとよくわかります。OpenGL と比べてみてください。

31 H<---------------->L 0
|  A  |  R  |  G  |  B  |       D3DFMT_A8R8G8B8

リトルエンディアンなので D3DFMT_A8R8G8B8 をメモリに格納すると B G R A の順番
になります。

なお Direct3D は A8B8G8R8 という逆順フォーマットも持っており、両方使うことが
出来ます。こちらは OpenGL の 8888 と全く同じ並びです。

31 H<---------------->L 0
|  A  |  B  |  G  |  R  |       D3DFMT_A8B8G8R8

ピクセルだけでなく、頂点形式でも D3DDECLTYPE_D3DCOLOR, D3DDECLTYPE_UBYTE4
と 2種類のフォーマットを選択できました。これも上記 2種類の並び順に対応します。

2種類の並び順を持っているのは基本的に 32bit 8888 だけです。
A32B32G32R32F 等の HDR/浮動小数系のフォーマットはすべて A B G R で
OpenGL と同じ順番で配置されています。


◎ Direct3D10 以降

Direct3D は上記のように 32bit 8888 の場合だけ配列が 2種類あります。
Direct3D10 以降は、HDR/浮動小数や OpenGL 同様の順番でほぼ統一されました。

まずフォーマットの表記が逆順になります。

 D3D9     D3D10/11/OpenGL
 ------------------------
 ARGB  →   BGRA
 ABGR  →   RGBA

そして 32bit 8888 のデフォルトの配列が RGBA (R8G8B8A8) になりました。
もちろんこれまで通り B8G8R8A8 も使えます。

31 H<---------------->L 0
|  A  |  B  |  G  |  R  |     DXGI_FORMAT_R8G8B8A8_UNORM  (== D3DFMT_A8B8G8R8)

31 H<---------------->L 0
|  A  |  R  |  G  |  B  |     DXGI_FORMAT_B8G8R8A8_UNORM   == D3DFMT_A8R8G8B8)

D3D9 以前と表記が逆になっているのでかなり ややこしい ことになります。
DDS テクスチャを作る場合、ほとんどのツールは Direct3D9 以前のフォーマット
表記になっているからです。

さらに Direct3D11 (DXGI1.1) では一見廃れるかと思われた B8G8R8A8 系が復活し
バリエーションが大幅に追加されています。


● Direct3D の pixel 配列 (16bit)

標準形式だった D3DFMT_A8R8G8B8 が ARGB の並びであることを思い出してください。
32bit で Alpha は最上位になります。
16bit にパックされた 565, 1555, 4444 も同様です。

31 H<---------------->L 0
|  A  |  R  |  G  |  B  |     D3DFMT_A8R8G8B8

15 H<-------->L 0
| A | R | G | B |             D3DFMT_A4R4G4B4

15 H<-------->L 0
|A| R  | G  | B |             D3DFMT_A1R5G5B5

15 H<-------->L 0
| R  |  G  | B  |             D3DFMT_R5G6B5

OpenGLES の形式と Alpha の位置が異なっています。
これで正しく表示できなかった原因が判明しました。
Alpha が存在しない 565 だけ正しく表示できたのも納得です。

独立したコンポーネントを Byte アクセスする OpenGL と違い、Direct3D は
リトルエンディアンの 32bit (DWORD) で読み込んだときに、互換のある形式に
なっているわけです。


● DDS の読み込み続き

原因が判明したのでローダーの続きです。
読み込みと同時に互換性のないピクセル並びを変換します。
まずは上で省略してしまった DDS ヘッダの定義。(詳しくはこちらをどうぞ)

#define	DDSCAPS_MIPMAP	0x00400000
struct T_DDSHEADER {
    unsigned int    dwMagic;
    unsigned int    dwSize;
    unsigned int    dwFlags;
    unsigned int    dwHeight;
    unsigned int    dwWidth;
    unsigned int    dwPitchOrLinearSize;
    unsigned int    dwDepth;
    unsigned int    dwMipMapCount;
    unsigned int    dwReserved1[11];
    unsigned int    dwPfSize;
    unsigned int    dwPfFlags;
    unsigned int    dwFourCC;
    unsigned int    dwRGBBitCount;
    unsigned int    dwRBitMask;
    unsigned int    dwGBitMask;
    unsigned int    dwBBitMask;
    unsigned int    dwRGBAlphaBitMask;
    unsigned int    dwCaps;
    unsigned int    dwCaps2;
    unsigned int    dwReservedCaps[2];
    unsigned int    dwReserved2;
};
#define	__DDS__MAGIC	0x20534444	// ' SDD'

ピクセルの入れ替えです。

template<unsigned int AShift, unsigned int AMask>
static void _flFASTCALL
DDStoGL16( unsigned int count, void* data )
{
    unsigned short*	ptr= reinterpret_cast<unsigned short*>( data );
    for(; count-- ;){
        unsigned int	color= *ptr;
        unsigned int	alpha= color & AMask;
        *ptr++= (color << (16-AShift)) | (alpha >> AShift);
    }
}

static void _flFASTCALL
DDStoGL32( unsigned int count, void* data )
{
    unsigned int*	ptr= reinterpret_cast<unsigned int*>( data );
    for(; count-- ;){
        unsigned int	color= *ptr;
        *ptr++= (color & 0xff00ff00)
                |((color >> 16) & 0xff)
                |((color << 16) & 0xff0000);
    }
}

変換が必要な場所で呼び出します。

  case 32: // 8888
        switch( dp->dwRBitMask ){
        ~
        case 0x00ff0000: // B G R A = DX
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            DDStoGL32( width*height, data );      // ← ここ
            break;
        }
        break;


    case 16:
        switch( dp->dwGBitMask ){
        case 0x00f0: // 4444
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_4_4_4_4;
            DDStoGL16<12,0xf000>( width*height, data );     // ← ここ
            break;
        case 0x07e0: // 565
            ~
        case 0x03e0: // 1555
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_5_5_5_1;
            DDStoGL16<15,0x8000>( width*height, data );     // ← ここ
            break;
        case 0x0000: // L A 88
            ~

このコードは入力したメモリを直接書き換えている点に注意です。

DDSLoader() は、読み込んだ DDS ファイルのメモリイメージをそのまま受け取ります。
メモリイメージはテンポラリ相当なので、この仕様で問題になることはあまりないかもしれません。
もし DDSLoader() 終了後もすぐにメモリを解放せずに、同じデータで何度も
DDSLoader() を呼び出すような再利用があれば問題が生じます。
安全のためには バッファを const で受け取り、変換が必要な場合のみバッファを複製するよう
書き換えが必要かもしれません。

今回のローダーは Mipmap に対応していないので DDSLoader() 最後の 2行は重要です。

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

デフォルトで MIPMAP 有効になっていることがあったためです。
明示的に MIPMAP を切っておかないと真っ黒なテクスチャが表示されることがあります。

長くなったので Mipmap 対応は次回にします。
書き込んでから気がつきました。24bit 888 の対応も忘れていました。


関連エントリ
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
D3D関連 DDSテクスチャの取り扱い
Direct3D もうひとつのユニファイド


Direct3D10 以降、Pixel フォーマットと頂点の各 Element のフォーマット形式は
統一されています。DXGI_FORMAT はまた Direct3D からも独立しており、
バージョンに依存せず同じものが使えるようになりました。
つまり Direct3D10 と Direct3D11 のフォーマットは同一です。

●Direct3D9

・ピクセル、インデックス用フォーマット D3DFORMAT
     D3DFMT_A8R8G8B8
     D3DFMT_R5G6B5
     D3DFMT_A16B16G16R16F
     D3DFMT_INDEX16    (IndexBuffer 用)
     等

・頂点フォーマット D3DDECLTYPE
     D3DDECLTYPE_FLOAT3
     D3DDECLTYPE_UBYTE4
     他


●Direct3D10/Direct3D11

・ピクセル、頂点、インデックス兼用  DXGI_FORMAT
     DXGI_FORMAT_R8G8B8A8_UNORM
     DXGI_FORMAT_B5G6R5_UNORM
     DXGI_FORMAT_R32G32B32_FLOAT  (従来の D3DDECLTYPE_FLOAT3 も兼ねる)
     DXGI_FORMAT_R16_UINT         (従来の D3DFMT_INDEX16 も兼ねる)
     ~

Direct3D11 ではライブラリ独自の頂点形式から ID3D11InputLayout を作るために
下記の関数を用意していました。
これをそのまま OpenGLES.20 用に置き換えたのが、先日の GLInputLayout の関数です。

// DXInputLayout.cpp
static struct _FormatType {
    DXGI_FORMAT   dxgi_format;
} formatTable[]= {

{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_UNKNOWN,			},

{	DXGI_FORMAT_R32_FLOAT,			},
{	DXGI_FORMAT_R32G32_FLOAT,		},
{	DXGI_FORMAT_R32G32B32_FLOAT,		},
{	DXGI_FORMAT_R32G32B32A32_FLOAT,		},

{	DXGI_FORMAT_R16_FLOAT,			},
{	DXGI_FORMAT_R16G16_FLOAT,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_FLOAT,		},

{	DXGI_FORMAT_R32_SINT,			},
{	DXGI_FORMAT_R32G32_SINT,		},
{	DXGI_FORMAT_R32G32B32_SINT,		},
{	DXGI_FORMAT_R32G32B32A32_SINT,		},

{	DXGI_FORMAT_R16_SINT,			},
{	DXGI_FORMAT_R16G16_SINT,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_SINT,		},

{	DXGI_FORMAT_R8_SINT,			},
{	DXGI_FORMAT_R8G8_SINT,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_SINT,		},

{	DXGI_FORMAT_R32_UINT,			},
{	DXGI_FORMAT_R32G32_UINT,		},
{	DXGI_FORMAT_R32G32B32_UINT,		},
{	DXGI_FORMAT_R32G32B32A32_UINT,		},

{	DXGI_FORMAT_R16_UINT,			},
{	DXGI_FORMAT_R16G16_UINT,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_UINT,		},

{	DXGI_FORMAT_R8_UINT,			},
{	DXGI_FORMAT_R8G8_UINT,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_UINT,		},

{	DXGI_FORMAT_R16_SNORM,			},
{	DXGI_FORMAT_R16G16_SNORM,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_SNORM,		},

{	DXGI_FORMAT_R8_SNORM,			},
{	DXGI_FORMAT_R8G8_SNORM,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_SNORM,		},

{	DXGI_FORMAT_R16_UNORM,			},
{	DXGI_FORMAT_R16G16_UNORM,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_UNORM,		},

{	DXGI_FORMAT_R8_UNORM,			},
{	DXGI_FORMAT_R8G8_UNORM,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_UNORM,		},
};


DXGI_FORMAT _flFASTCALL
PixelToFormat( unsigned int func )
{
    assert( func < Image::FUNC_END );
    return  formatTable[ func ].dxgi_format;
}

void _flFASTCALL
SetElementDesc(
	D3D11_INPUT_ELEMENT_DESC* desc,
	unsigned int desccount,
	const a5::InputLayout& layout
	)
{
    unsigned int    ecount= layout.GetElementCount();
    assert( ecount < desccount );
    for( unsigned int ei= 0 ; ei< ecount ; ei++, desc++ ){
        const a5::InputElement*  ep= layout.GetElement( ei );
        desc->SemanticName= ep->SemanticName;
        desc->SemanticIndex= ep->SemanticIndex;
        desc->Format= PixelToFormat( ep->pixf.Func );
        desc->InputSlot= 0;
        desc->AlignedByteOffset= ep->ByteOffset;
        desc->InputSlotClass= D3D11_INPUT_PER_VERTEX_DATA;
        desc->InstanceDataStepRate= 0;
    }
}

このように、独自の頂点形式をテーブルで DXGI_FORMAT に変換しています。
DXGI そのままでないのが幸いして、OpenGLES2.0 への変換も比較的うまく
いっています。OpenGLES2.0 で用意したのが下記のコードです。
このテーブルはそのまま上の DXGI_FORMAT に対応しています。

static struct _FormatType {
    GLenum    dxgi_format;
    unsigned int   component;
    unsigned int   normalize;
} formatTable[]= {

{    0,                  0,    FALSE,    },
{    0,                  0,    FALSE,    },
{    0,                  0,    FALSE,    },
{    0,                  0,    FALSE,    },

{    GL_FLOAT,           1,    FALSE,	},
{    GL_FLOAT,           2,    FALSE,	},
{    GL_FLOAT,           3,    FALSE,	},
{    GL_FLOAT,           4,    FALSE,	},

{    0,                  0,    FALSE,	},  // half float
{    0,                  0,    FALSE,	},  // half float
{    0,                  0,    FALSE,	},  // half float
{    0,                  0,    FALSE,	},  // half float

{    0,                  0,    FALSE,	},  // signed int 32
{    0,                  0,    FALSE,	},  // signed int 32
{    0,                  0,    FALSE,	},  // signed int 32
{    0,                  0,    FALSE,	},  // signed int 32

{    GL_SHORT,	         1,    FALSE,	},  // signed int 16
{    GL_SHORT,	         2,    FALSE,	},  // signed int 16
{    GL_SHORT,	         3,    FALSE,	},  // signed int 16
{    GL_SHORT,	         4,    FALSE,	},  // signed int 16

{    GL_BYTE,	         1,    FALSE,	},  // signed byte 8
{    GL_BYTE,	         2,    FALSE,	},  // signed byte 8
{    GL_BYTE,	         3,    FALSE,	},  // signed byte 8
{    GL_BYTE,	         4,    FALSE,	},  // signed byte 8

{    0,		         0,    FALSE,	},  // unsigned int 32
{    0,		         0,    FALSE,	},  // unsigned int 32
{    0,		         0,    FALSE,	},  // unsigned int 32
{    0,		         0,    FALSE,	},  // unsigned int 32

{    GL_UNSIGNED_SHORT,  1,    FALSE,	},
{    GL_UNSIGNED_SHORT,  2,    FALSE,	},
{    GL_UNSIGNED_SHORT,  3,    FALSE,	},
{    GL_UNSIGNED_SHORT,  4,    FALSE,	},

{    GL_UNSIGNED_BYTE,   1,    FALSE,	},
{    GL_UNSIGNED_BYTE,   2,    FALSE,	},
{    GL_UNSIGNED_BYTE,   3,    FALSE,	},
{    GL_UNSIGNED_BYTE,   4,    FALSE,	},

{    GL_SHORT,           1,    TRUE,	},
{    GL_SHORT,           2,    TRUE,	},
{    GL_SHORT,           3,    TRUE,	},
{    GL_SHORT,           4,    TRUE,	},

{    GL_BYTE,            1,    TRUE,	},
{    GL_BYTE,            2,    TRUE,	},
{    GL_BYTE,            3,    TRUE,	},
{    GL_BYTE,            4,    TRUE,	},

{    GL_UNSIGNED_SHORT,  1,    TRUE,	},
{    GL_UNSIGNED_SHORT,  2,    TRUE,	},
{    GL_UNSIGNED_SHORT,  3,    TRUE,	},
{    GL_UNSIGNED_SHORT,  4,    TRUE,	},

{    GL_UNSIGNED_BYTE,   1,    TRUE,	},
{    GL_UNSIGNED_BYTE,   2,    TRUE,	},
{    GL_UNSIGNED_BYTE,   3,    TRUE,	},
{    GL_UNSIGNED_BYTE,   4,    TRUE,	},
};


GLenum _flFASTCALL
PixelToFormat( unsigned int func, unsigned int* component, unsigned int* normal )
{
    assert( func < Image::FUNC_END );
    const _FormatType*   fp= &formatTable[ func ];
    if( component ){
        *component= fp->component;
    }
    if( normal ){
        *normal= fp->normalize;
    }
    return  fp->dxgi_format;
}

前回 GLInputLayout の CreateInputLayout() の中で呼び出していた
PixelToFormat() がこれです。
まだ FLOAT しか使っていないので本当に互換性があるかどうかは検証してません。
BYTE の並びは D3D9 の D3DDECLTYPE_UBYTE4 と D3DDECLTYPE_D3DCOLOR
のように順番が異なっている可能性があります。

同じく前回「ELEMENTFLAG_NORMALIZE の定義値が 1 なのは念のため。」と書いた
のはコンパイラの最適化で三項演算子が完全に消えることを期待していたからです。

(ep->Flag & ELEMENTFLAG_NORMALIZE) ? GL_TRUE : GL_FALSE

GL_TRUE / GL_FALSE の定義は 1 と 0 なので、最終的には
「ep->Flag & ELEMENTFLAG_NORMALIZE」だけが残ります。
予想通り分岐は消えますが、シンボルが 1 以外でも shr が一つ追加されるだけでした。

; ELEMENTFLAG_NORMALIZE == 4 の場合 (x64 + VC)
000000013F4D6697  shr         ebx,2 
000000013F4D66A1  and         ebx,1 
000000013F4D66A4  mov         edx,ebx 

シフトを命令に埋め込める ARM だったら全く意識する必要ないのかもしれません。


関連エントリ
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.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<unsigned char>( layout.GetStrideSize() );
        edesc->Components= static_cast<unsigned char>( component );
        edesc->ByteOffset= static_cast<unsigned char>( 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 シェーダー管理


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 はたくさんあります。
基本的な 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 画面解像度とタッチ


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


●キーボードの配列

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 で扱える 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<float,4> 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 を 4x4 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 には 4x4 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


Windows 版 vim を使っています。
といってもちょっと便利な vi 程度にしか使っておらず、カスタマイズは最小限で
済ませています。コマンドシェルから gvim を起動してファイル毎に別のエディタを
開くスタイルです。
shell + gvim の組み合わせは X68000 の Ko-Window 上で vi (stevie) を使う
場合とよく似ています。shell と別ウィンドウでいくつでも開けるからです。
ほとんど同じ感覚で使用しています。

vim は vi 系のテキストエディタです。
コマンドシェルからヒストリを使ってエディタを呼び出していると、すでに開いている
ファイルなのに、つい多重で開いてしまうことがあります。
同じファイルを複数のエディタで同時に開くのはまずいです。
最後に保存した方の更新しか残らないからです。
stevie の時はよくミスしがちでした。

幸い vim の場合はあまり困ったことにはなりません。
ファイルを開くと .swp ファイルが作られるので、そのファイルがすでに編集中なのか
わかります。すでに .swp ファイルがあるなら、このまま開くか ReadOnly
で続けるか、起動を中止するか確認してくれます。

便利な反面、衝突するたびに動作を選択して中止し、起動中のウィンドウから
目的のものを探し出すのはだんだん面倒になってきます。
そこで、下記のようなプログラムを作って使っていました。

・指定したファイルがすでに gvim で開いているかどうか調べる
  ・もしすでに存在していたら、そのウィンドウをアクティブにする
  ・無ければ新たに gvim でそのファイルを開く

もしかしたら vim 自体に何らかの設定があったり、最初から似たような機能が付いて
いたり、または同等のものが存在しているのかもしれません。
必要あるかどうかわかりませんが置いておきます。

runvim100.zip

使い方
runvim [option] [<ファイル名>]

 -e<起動するgvimのパス>

たとえば alias や shell の関数定義で下記のように定義しておきます。
(gvim のパスは任意)

alias vi='runvim.exe -eC:/app/Vim71/gvim.exe'

上記定義した後「vi ファイル名」で起動すると、すでに開いていれば対応する
gvim を最前面に持ってきて入力フォーカスします。
無ければ gvim を通常起動します。


以下 vim (7.1) の個人的な設定です。
Vim 自体は本家 vim.org からダウンロードしたパッケージをそのままインストール
しています。

" $HOME/_vimrc
se ai columns=80 lines=46 hls nobackup ruler
se guifont=MS_ゴシック:h8:cSHIFTJIS
se linespace=0
se guioptions=grLt
se mouse=a
se iminsert=0 imsearch=0
se enc=cp932
se guioptions-=a
se statusline=%<%f\ %m%r%h%w%{'['.(&fenc!=''?&fenc:&enc).']['.&ff.']'}%=%b\ %B\ %l,%c%V%8P
lan en
lan cty en
lan mes en
lan tim ja
syntax enable
hi Constant	guifg=#a03800
hi PreProc	guifg=#008010
hi Folded	guifg=#808000 guibg=#fffffe
hi FoldColumn	guifg=#808000 guibg=#fffffe
so $HOME/format.vim
let format_join_spaces=2
se foldmethod=syntax

実はフォントは難ありで、この設定だと小文字のエル=l と大文字のアイ=I の区別が
付かないけど慣れました。このままプログラム書いてます。

format.vim は日本語で join したときに空白が入らないようにするため使用しています。

doxygen syntax を追加しています。
Vim を install したフォルダ内の filetype.vim を書き換えます。
"setf cpp" や "setf c" と定義されているところを全部 "setf cpp.doxygen"
のように変更します。".doxygen" を追加するわけです。
これで syntax として cpp が選択されると同時に doxygen のキーワードハイライト
が有効になります。

折りたたみ機能も利用しています。
そのまま folding を有効にすると C ソースコードのブロックも折りたたまれて
しまうので、その設定は削除。その代わり doxygen のコメントを部を折りたためる
ようにしています。

(1) syntax/c.vim の中の syntax ~ fold ~ と書かれた行の "fold" を全部削除
  たとえば下記のように (syn ~ で始まる場合もあり)
syntax region	cBlock		start="{" end="}" transparent fold
 ↓
syntax region	cBlock		start="{" end="}" transparent

(2) syntax/doxygen.vim に下記の 2行を追加
syn region myDoxFold start="/\*!" end="\*/" transparent fold keepend
syn sync fromstart

折りたたみ操作は vim 上で zr または zm です。


.hlsl や .fx 等、自分が使うファイルの syntax も追加しています。
専用の syntax ファイルが無くても、C言語系なら c や cpp を割り当てるだけで
見やすくなります。
たとえば filetype.vim を書き換えてこんな風に。(fsc は自前の C言語スクリプト)
" filetype.vim
au BufNewFile,BufRead *.fsc			setf cpp.doxygen


Vim の syntax や script ファイルは下記ページで検索できます。

vim.org Scripts Browse all


関連エントリ
vi の話


まだ未対応らしく Windows7 にはうまくインストールできませんでした。

ATI Stream Software Development Kit (SDK) v2.0 Beta Program

インストーラが正常終了しても Program Files の ATI Stream フォルダが
空のままです。
とりあえず Vista で展開したのちパスを通せば使えなくはないようです。
よく見ると OpenCL は GPU 未対応で CAL も無くなっています。
Intel CPU (+GeForce) でも動きました。

CL_PLATFORM_PROFILE    = FULL_PROFILE
CL_PLATFORM_VERSION    = OpenCL 1.0 ATI-Stream-v2.0-beta2
CL_PLATFORM_NAME       = ATI Stream
CL_PLATFORM_VENDOR     = Advanced Micro Devices
CL_PLATFORM_EXTENSIONS = 

CL_DEVICE_TYPE = 2
CL_DEVICE_VENDOR_ID = 4098
CL_DEVICE_MAX_COMPUTE_UNITS = 8
CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS = 3
CL_DEVICE_MAX_WORK_ITEM_SIZES = 1024 1024 1024
CL_DEVICE_MAX_WORK_GROUP_SIZE = 1024
CL_DEVICE_EXECUTION_CAPABILITIES = 1

Core i7 の結果です。
CL_DEVICE_TYPE の 2 は CPU (CL_DEVICE_TYPE_CPU) を表しています。
CL_DEVICE_MAX_COMPUTE_UNITS は 8個。
Core2 Duo で走らせると CL_DEVICE_MAX_COMPUTE_UNITS = 2 となります。

Device Type は下記の通り 3種類定義されています。

2 CL_DEVICE_TYPE_CPU
4 CL_DEVICE_TYPE_GPU
8 CL_DEVICE_TYPE_ACCELERATOR

Compute Shader は GPU 上で走るプログラムを共通化するのが目的でした。
OpenCL は CPU/GPU やその他を含めた、より幅広いプロセッサを対象として
作られているようです。

Direct3D の Compute Shader も CPU でエミュレーションできれば同じですが
現状だと WARP (10.1) も対応しておらず、Reference でしか動作しません。
エミュレーションできても GPU, CPU 混在しての利用は想定されていないといって
良いでしょう。

CL_DEVICE_EXECUTION_CAPABILITIES は CL_EXEC_KERNEL のみセットされています。
CL_EXEC_NATIVE_KERNEL が無いので、clEnqueueNativeKernel()
は実行出来ないようです。

バッファの転送や実行は Command Queue を生成して非同期に監視します。
GPU の描画操作に似ています。
異なるのは Out of order で実行可能なこと。
そのままでは実行順の保証が出来ませんが、その代わり各コマンドに依存関係を
設定できます。特定のコマンドの完了を待ってから実行できるわけです。

Direct3D11 でも DeviceContext が分離され、Command List (Queue) に
蓄えられる GPU 命令と、そうでないものを区別できるようになっています。
ただこちらは、複数のスレッドで作成したコマンドを単一のコンテキストで実行する
ための仕組みです。コマンドバッファ内の実行順は決まっているため、
スケジューリングは呼び出す側に委ねられています。

OpenCL の kernel の記述言語は glsl とは別もので、より C言語に近いものです。
long は 64bit。vector も 16個まで扱えるようです。
4コンポーネントを超えた場合、各要素は16進数指定で s0, s1,~ sf といった表記
になっています。

D3D の Compute Shader はデバイスやリソース管理等を D3D のコンポーネントに
委ねています。Direct3D の一部であって言語も hlsl そのまま同じものでした。

OpenCL の場合完全に独立しており、リソース管理も言語も OpenCL の世界で作られています。
コマンドをいくつか拡張してレンダリング向け補助機能を追加すれば、新しい
3D API セットができるのではないかと思うくらい。
その分 ATI CAL とか、独自 SDK と比べると複雑になった印象を受けるかもしれません。
リソースは描画と共有可能で、D3D や OpenGL からも OpenCL のバッファを作成
出来るようになっています。

const int THREAD_SIZE= 32;
cl_int	  status= 0;
cl_uint	  nums;
cl_platform_id	pid= 0;
cl_device_id	device= 0;

status= clGetPlatformIDs( 1, &pid, &nums );
status= clGetDeviceIDs( pid, CL_DEVICE_TYPE_CPU, 1, &device, &nums );

cl_context  context= clCreateContextFromType( NULL, CL_DEVICE_TYPE_CPU, NULL, NULL, &status );
cl_command_queue   command= clCreateCommandQueue( context, device, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, &status );

cl_mem	mem= clCreateBuffer( context, CL_MEM_READ_WRITE, THREAD_SIZE * sizeof(cl_float), NULL, &status );

const char*   src=
    "__kernel void main( __global float* op )\n"
    "{\n"
    "   unsigned int   tid= get_global_id(0);\n"
    "   op[tid]= tid * tid;\n"
    "}";
size_t	srcsize= strlen( src );
cl_program  prog= clCreateProgramWithSource( context, 1, &src, &srcsize, &status );

status= clBuildProgram( prog, 0, NULL, NULL, NULL, NULL );

cl_kernel  kernel= clCreateKernel( prog, "main", &status );

status= clSetKernelArg( kernel, 0, sizeof(cl_mem), &mem );

size_t	gthread= THREAD_SIZE;
size_t	lthread= 1;
cl_event   event;
status= clEnqueueNDRangeKernel( command, kernel, 1, NULL, >hread, <hread, 0, NULL, &event );

cl_event   event2;
cl_float   memptr2[THREAD_SIZE];
status= clEnqueueReadBuffer( command, mem, CL_TRUE, 0, THREAD_SIZE * sizeof(cl_float), memptr2, 1, &event, &event2 );

status= clWaitForEvents( 1, &event2 );
clReleaseEvent( event );
clReleaseEvent( event2 );

for( int i= 0 ; i< THREAD_SIZE ; i++ ){
    printf( "%d: %f\n", i, memptr2[i] );
}

status= clReleaseKernel( kernel );
status= clReleaseProgram( prog );
status= clReleaseMemObject( mem );
status= clReleaseCommandQueue( command );
status= clReleaseContext( context );



HP TouchSmart IQ800 で Multi touch を試すには Windows7 が必要ですが
ノート用 GPU を使っているせいかグラフィックドライバがうまく入らない
ことがあります。

HP TouchSmart PC IQ800

GeForce 9600M GS に対応しているはずの 186.03 もそのままでは install できません。
Vista 用ドライバも使えるので、もともと付属している純正ドライバは入れられます。
プリインストールされている Vista のドライブパーティションを残してあるなら
\hp\drivers\nVidia_Graphics がそれです。

IQ827jp の場合付属しているのは WindowsVista x86 (32bit) なので、上記ドライバも
x86 のみです。Windows7 x64 を入れる場合は使えませんでした。
HP のサイトから IQ817jp 用の Vista x64 ドライバを落とせますがこれもだめです。

Windows7 beta のときはこんな感じでドライバを入れました。

一応 186.03 も inf を書き換えれば何とかなりそうです。
もともと GeForce 9600M GS に対応していると書かれているので
inf ファイルからそれらしき記述を探します。
まず nvac.inf を調べてみます。

NVIDIA_DEV.0648.** というシンボルが 9600M GS のようです。

デバイスの ID が一致しているかどうか確認します。

デバイスマネージャー → Display adapters
   → Standard VGA Graphics Adapter → プロパティ
details タブ Hardware Ids を見ると ID がわかります。& が区切り。

VEN = 10DE
DEV= 0648
SUBSYS= 900F1043

DEV が 0648 なので上で調べたシンボルと一致しています。
対応する SUBSYS が無いためそのままインストールできなかったのだと思われます。
9600M GS 相当の定義エントリ「%NVIDIA_DEV.0648.**% = ~」の行を複製し、
「**」の番号を別の重複しない値 (今回は18) に書き換えます。
SUBSYS_ の後ろを 900F1043 に置き換えることで一応インストールできる
ようになりました。


%NVIDIA_DEV.0648.18% = Section005, PCI\VEN_10DE&DEV_0648&SUBSYS_900F1043

行を複製した部分は 3カ所です。そのうち SUBSYS を書き換えたのは 2カ所。
nvac.inf 以外にも inf ファイルは多数存在し、他にも 9600M GS の記述がみられます。
本当に nvac で良いのか根拠は全くないです。
何らかの問題が生じる可能性もあるので、もし試す場合は必ず自己責任でお願いします。
早く公式な Windows7 ドライバが出てくれると良いのですが。

Aero にするにはドライバインストール後にシステム評価の再計測が必要です。

WDDM 1.1

CPU: 6.0
RAM: 6.0
AERO: 6.3
GAME: 6.3
HDD: 5.9


関連エントリ
Windows7 Multitouch API (3)
Windows7 とマルチタッチ / HP TouchSmart PC IQ800


Windows7 を RTM 版に入れ替えたら D3D11(beta) のプログラムが動かなくなって
いました。DirectX SDK March2009 + D3D11 を使用しており、RC まではきちんと
動いていたものです。

止まっているのが CommandList まわりの呼び出しで関係ない関数に飛んでいます。
lpVtbl がずれているような感じ。
単に beta と互換性がなくなっているだけかもしれません。
新しい Windows7 SDK には D3D 関連のヘッダや lib も含まれているため、
こちらを使えば RTM 版 dll が用いられます。
d3dx11 や d3dcompiler は無いので、この辺を使っている場合は DirectX SDK も
併用することになります。core ではないためおそらく大丈夫だと思います。

でも結局 Windows7 SDK の d3d11.lib に切り替えてもだめ。
コンソールをみると d3d11.dll だけでなく最後に d3d11_beta.dll も読み込まれて
いました。この両者が混在しているのはあまりよい感じではありません。

DirectX SDK サンプルは動くしサンプルをビルドしても大丈夫です。
サンプルとの違いは DXGI1.1 を使っていることくらいでしょうか。
試しに DXGI1 に戻してもやっぱり動きません。
DXGI1.1 は Windows SDK 側の dxgi.lib で使用できるはずなので、問題ないと
思います。

lib の検索順番や、リンクする lib の組み合わせをいろいろテスト。
気になるのは Windows SDK 側の d3d11.lib のみ使うようにしても、必ず最後に
d3d11_beta.dll が読み込まれてしまうことです。
誰が読み込んでいるのだろうと調べているうちに DebugLayer が原因ではないかと
思いつきました。

D3D11Create~() で D3D11_CREATE_DEVICE_DEBUG を指定していると、
Debug でも Release build でも最後に d3d11_beta.dll が読み込まれています。
とりあえずこのフラグ指定を消すと Windows7 RTM で動作しました。
やはり d3d11_beta.dll との互換性が問題だと思われます。

DirectX SDK がリリースされるまで待った方が良さそうです。


OpenGLES の頂点データは各頂点要素毎の配列を登録します。
頂点座標や法線など、頂点の構成要素は Direct3D では element、OpenGL では
attribute と呼ばれているようです。

glVertexAttribPointer( vloc, 3, GL_FLOAT, GL_FALSE, sizeof(vec3), v_data );
glVertexAttribPointer( nloc, 3, GL_FLOAT, GL_FALSE, sizeof(vec3), n_data );
glVertexAttribPointer( tloc, 2, GL_FLOAT, GL_FALSE, sizeof(vec2), t_data );

Direct3D の場合は基本的にパックされた頂点データを用います。
いわゆる AOS で、対する OpenGL はベクター単位の SOA になります。
とはいえ実際には Direct3D でも複数の頂点ストリームを与えることが出来るので、
OpenGL のように要素毎に配列を分離することが可能です。

また OpenGL の場合も index は共有なので頂点の位置や配列の大きさも同数です。
stride を指定すれば、Direct3D のようなパックした頂点データのポインタを登録
することができます。
どちらも出来ることや使い方にほとんど差が無くなっています。

struct vtype {
    float x, y, z;
    float nx, ny, nz;
    float tu, tv;
};
glVertexAttribPointer( vloc, 3, GL_FLOAT, GL_FALSE, sizeof(vtype), &vp->x );
glVertexAttribPointer( nloc, 3, GL_FLOAT, GL_FALSE, sizeof(vtype), &vp->nx );
glVertexAttribPointer( cloc, 2, GL_FLOAT, GL_FALSE, sizeof(vtype), &vp->tu );

普段 Direct3D 向けにデータを出力している関係上、座標系と同じように D3D と同じ
データが使えれば楽出来ます。
問題はどちらが効率がよいのかと言うこと。
D3D 形式の頂点の持ち方で速度が落ちないかどうかが心配な点です。

キャッシュの利用効率を考えると、一見インターリーブされた D3D タイプの方が
良さそうに見えます。
一度にアクセスするであろうデータが適切な局所性を持つからです。
頂点は index によるランダムアクセスになる可能性があるので、SOA の配列の効率が
必ずしも最善とは限りません。でも GPU は 1頂点分まとめてデータを読み込む
ことがわかっているので、一カ所に集まっていた方がキャッシュを活用できます。

以前そう考えて最適化のつもりで試したものの、逆に遅くなってしまうハードウエア
がありました。インデックスも個別に持てたので、頂点ストリーム毎にキャッシュを
持つなどの特殊な仕組みだったのかもしれません。もしそうなら D3D タイプの
インターリーブは同じデータが重複してキャッシュに乗ることになってしまいます。

ただこれは特殊なケースかもしれません。いろいろ見てみると、やはり最適化手法
としてインターリーブを推奨している場合もあるようです。
普通は D3D と同形式でデータを扱って問題ないと思われます。
これでデータもライブラリコードも D3D と全く同じように扱う準備が出来ました。


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


先日の SSE 命令の実行速度を試してみました。
あまり厳密なものではなくて、掲載したコード 3種類をそのままループで回しただけです。

● Core2 Duo P8600 2.4GHz Windows7 x86

x86  SSE1       17581 (msec)  mulps+addps
x86  SSE3       20623         haddps
x86  SSE4.1     17799         dpps

● Core i7 940 2.93GHz Windows7 RC x64

x86  SSE1       14320         mulps+addps
x86  SSE3       11716         haddps
x86  SSE4.1     13682         dpps

x64  SSE1       17269         mulps+addps
x64  SSE3       11076         haddps
x64  SSE4.1     13681         dpps

単位は時間で値が小さい方が高速です。

Core2 実行時、SSE3 hadd で遅くなってしまい少々焦りました。
昨日これで良いって書いたばかりなのに。結局元のコードの方が速いという結果に。
Core i7 の場合は正反対で hadd が最速になっています。

おかしなことに x64 のみ SSE1 で極端に速度が落ちています。
実際に x64+SSE1 の展開されたコードを見てみると、x64 で増えたレジスタを最大限
使おうとしてループの先頭で全部レジスタにロードしていました。
その分転送などの命令が他のケースよりも増えています。

このテストは局所的な小さいループかつ、使用したデータもストリーム入出力ではなく
同じ領域へのたたみ込みでした。
それゆえすべてのデータがキャッシュに乗った状態だと考えられます。
レジスタ間の転送命令が増えるよりもおそらくキャッシュに乗ったメモリから
読み直した方が速いのでしょう。

実際に SSE 命令が使われるケースだとストリーム処理が多いため、必ずしも
このような結果にはならないと思われます。

haddps , dpps の実行性能は CPU によって逆転することがわかりました。
この両者は比較的似たようなコードに展開されています。
ループ内の命令数も同数でした。
プロセッサによって異なるため、やはり指針としては命令数を減らすことを考えて
書くのが良さそうです。

関数は intrinsics 命令で記述しためコンパイラは inline 展開しており、
複数の関数に渡って並べ替えなどの最適化が行われています。
intrinsic 命令がどのように展開されるかだけ把握しておけば asm を使わない方が
良いかもしれません。x64 だと使えないし。
_mm_setr_ps とかは結構命令数食います。


関連エントリ
Direct3D Matrix の並びと SSE 命令
Intel AVX その3 命令
D3D10 row_major column_major
SSE についてのメモ(2) SSE4など


Matrix の並びには row major と column major の2種類あります。
たまにどっちがどっちかわからなくなりますが、row major が「三」で
column major が「川」。
演算上はどちらも違いは無くデータ配列が異なっているだけです。
特にシェーダーのコード上は大差ないです。演算もデータ構造も使う側に委ねられて
いてどちらでも使えます。

シェーダーコンパイラ fxc は /Zpr, /Zpc のオプションで並びの方向を指定する
ことが出来ます。
このオプションによって、たとえば vector * matrix の乗算が dp (dot product)
に展開されるか mad に展開されるか切り替わります。
どっちの設定だろうが乗算の前後を入れ替えれば良いだけなので、正直自分の記述と
デフォルトが異なっている場合にオプションで入れ替えればよい、とそのくらいの
認識でした。

fxc のデフォルトは /Zpc で column-major になっています。
上の演算であれば mad に展開されます。

シェーダーの場合 dp, mad どちらでも基本的に演算命令数は変わらず、効率の差は
生じません。ただ mad の方が vector の w=1 の代入が簡略化されるケースがある
ため小さくなることがあります。
これがそのまま GPU ネイティブコード上でも影響があるかどうかはまた別です。
AMD stream のように swizzle 時に直接 w に 1 を書き込めるなら差は無くなると
思います。

; opos= mul( mat, float4(v.xyz,1) );

; /Zpr
mov r0.xyz, cb0[4].xyzx
mov r0.w, l(1.000000)
dp4 o0.x, cb0[0].xyzw, r0.xyzw
dp4 o0.y, cb0[1].xyzw, r0.xyzw
dp4 o0.z, cb0[2].xyzw, r0.xyzw
dp4 o0.w, cb0[3].xyzw, r0.xyzw

; /Zpc
mul r0.xyzw, cb0[1].xyzw, cb0[4].yyyy
mad r0.xyzw, cb0[0].xyzw, cb0[4].xxxx, r0.xyzw
mad r0.xyzw, cb0[2].xyzw, cb0[4].zzzz, r0.xyzw
add o0.xyzw, r0.xyzw, cb0[3].xyzw

Direct3D ではもともと column major の形式で D3DMATIRX が定義されていました。
D3D9 以前、固定機能パイプラインでは API に渡すデータ並びを共通化する必要が
あったためと思われます。
そのため自前のライブラリでもほぼ D3DMATRIX 互換で使っていました。

typedef struct _D3DMATRIX {
    union {
        struct {
            float   _11, _12, _13, _14;
            float   _21, _22, _23, _24;
            float   _31, _32, _33, _34;
            float   _41, _42, _43, _44;

        };
        float m[4][4];
    };
} D3DMATRIX;

ただし実際はターゲットハードウエアやライブラリの仕様に合わせたり、最適化の
ために row major 形式に変換することが多かった思います。
シェーダーの演算上は大差なくても contant buffer の利用効率に影響があるからです。
昔は Constant Buffer が非常に小さかったため、index で参照する matrix は
4x3 でないとほとんど入りませんでした。
そのため Bone として使う geometry 用の matrix 乗算ルーチンをライブラリに
加えています。これは乗算結果を転置の 4x3 で返す専用命令です。

そろそろ D3DMATRIX 互換性もいらないかなと思い、最初から全部 row major で
持つようにライブラリを書き換えてみました。

基本的には自前の Matrix を下記のように定義し直すだけで、メンバにアクセスして
いるコードはほぼそのまま動きます。

struct {
    float  _11, _21, _31, _41;
    float  _12, _22, _32, _42;
    float  _13, _23, _33, _43;
    float  _14, _24, _34, _44;
};

修正が必要なのは、部分的に vector みなしてアクセスしていた処理と、SSE 命令で
記述していた各種関数です。
4x4 の matrix 同士の乗算では修正は不要ですが、それ以外はいろいろ見直す必要が
ありそうです。
たとえば vector との乗算だと、mad ではなく dp 相当の水平演算になります。

SSE4.1 の dpps を使ってみました。SSE4 のヘッダは smmintrin.h。
実命令数がわかるように dest 側のレジスタを共通にしています。

// SSE4.1
__m128 xmm0;
xmm0= _mm_load_ps( &v.x );

xmm1= _mm_load_ps( &_11 );
xmm1= _mm_dp_ps( xmm1, xmm0, 0xf1 );

xmm2= _mm_load_ps( &_12 );
xmm2= _mm_dp_ps( xmm2, xmm0, 0xf2 );
xmm1= _mm_add_ps( xmm1, xmm2 );

xmm2= _mm_load_ps( &_13 );
xmm2= _mm_dp_ps( xmm2, xmm0, 0xf4 );
xmm1= _mm_add_ps( xmm1, xmm2 );

xmm2= _mm_load_ps( &_14 );
xmm2= _mm_dp_ps( xmm2, xmm0, 0xf8 );
xmm1= _mm_add_ps( xmm1, xmm2 );

Shader のように簡単に記述できますが SSE4.1 対応 CPU (Penryn 以降) でないと
動きません。
試しに SSE3 の haddps を使ってみました。

// SSE3
xmm0= _mm_load_ps( &v.x );

xmm1= _mm_load_ps( &_11 );
xmm1= _mm_mul_ps( xmm1, xmm0 );

xmm2= _mm_load_ps( &_12 );
xmm2= _mm_mul_ps( xmm2, xmm0 );

xmm1= _mm_hadd_ps( xmm1, xmm2 );

xmm3= _mm_load_ps( &_13 );
xmm3= _mm_mul_ps( xmm3, xmm0 );

xmm2= _mm_load_ps( &_14 );
xmm2= _mm_mul_ps( xmm2, xmm0 );

xmm3= _mm_hadd_ps( xmm3, xmm2 );
xmm1= _mm_hadd_ps( xmm1, xmm3 );

全く同じ命令数でした。
SSE3 対応 PC の方が多いしこっちの方が良さそうです。
SSE4 命令は命令長が長いので生成バイナリも小さくなります。
ただもっと複雑なケースだと、入力を自由に選択できたり出力マスク可能な dpps
の方が融通が利いて少ない命令で書けるようです。
以前はこんな感じ。

// SSE1 (column major)
xmm0= _mm_load_ps( &v.x );

xmm1= xmm0;
xmm1= _mm_shuffle_ps( xmm1, xmm1, _MM_SHUFFLE(0,0,0,0) );
xmm2= _mm_load_ps( &_11 );
xmm1= _mm_mul_ps( xmm1, xmm2 );

xmm3= xmm0;
xmm3= _mm_shuffle_ps( xmm3, xmm3, _MM_SHUFFLE(1,1,1,1) );
xmm2= _mm_load_ps( &_21 );
xmm3= _mm_mul_ps( xmm3, xmm2 );
xmm1= _mm_add_ps( xmm1, xmm3 );

xmm3= xmm0;
xmm3= _mm_shuffle_ps( xmm3, xmm3, _MM_SHUFFLE(2,2,2,2) );
xmm2= _mm_load_ps( &_31 );
xmm3= _mm_mul_ps( xmm3, xmm2 );
xmm1= _mm_add_ps( xmm1, xmm3 );

xmm0= _mm_shuffle_ps( xmm0, xmm0, _MM_SHUFFLE(3,3,3,3) );
xmm2= _mm_load_ps( &_41 );
xmm0= _mm_mul_ps( xmm0, xmm2 );
xmm1= _mm_add_ps( xmm1, xmm0 );

matrix 同士の乗算も水平演算命令で書けるかと思い少々考えてみましたがレジスタ
が足りなくてうまくいっていません。
x64 だとレジスタ数が倍あるため intrinsic で書いたとおりのコードに展開されます。
x86 では待避のためのスタックが使われていました。


関連エントリ
Intel AVX その3 命令
D3D10 row_major column_major
SSE についてのメモ(2) SSE4など


今日は不調でした。

●繊細な WiMAX

接続が切れる場所は結構あります。
移動中安定して使えないのは、ハンドオーバーが出来ないからではなく
圏外になる区間がまだあちこちあるからです。
エリア以外でも、たとえば停車中でも併走する路線に電車が横付けされると
その間通信が止まったりすることがあります。
同じ電車でも車両や場所、混み具合、上り下りの違いでも影響があるようです。

ただ一時的に通信が止まっても、圏内になって電波をつかめばまた通信は始まります。
スムーズに復帰さえできれば十分で、全く使えないってことはなく使えます。
というのも、つながる場所も切れる場所も毎日通っているとほぼ把握できるから。
つながればそれなりに速いので、いつ更新しておけばいいのかタイミングがわかります。


●問題再発

URoad-5000 の問題は WiMAX とはまた別のところにあるようです。
何らかのタイミングでアダプタが反応しなくなることがあります。

・UD01SS の LED が点灯したまま
・URoad-5000 の WiMAX 電波受信 LED は直前の状態で固定 (変化しなくなる)

一時的に通信が途切れて停止し、その後復帰のための初期化待ちで失敗している
ように見えます。
無線 LAN は生きており、WiMAX のアンテナランプも点灯したままなので一見
きちんとつながっているように見えます。でも通信しても反応はなし。
新宿駅でホームに別の電車が入ってきたときに通信が止まり、その後目的地に
着くまで復活しませんでした。

一度 UD01SS を抜いて差し直したのち、しばらく待つと復活しました。
UD01SS を差し直した直後、しばらく UD01SS の LED が点灯したままになる点に
注意です。またハングアップ状態かと思いがちですが根気よく待ちます。

初日にほとんど使えなかったのも、これが原因だったと思われます。
VAIO type P で UD01SS を使用していたときはこのような症状は出ていませんでした。


●LED の読み方

URoad-5000 の本体 LED と、UD01SS の LED 両方から状態を判断する必要があります。

WiMAX アダプタ UD01SS 本体の青色 LED

(1) URoad-5000 の電源が入っていない状態 - 消灯
(2) URoad-5000 の電源を入れてもまだ初期化されていない状態 - 消灯
(3) URoad-5000 の無線も起動し、WiMAX の初期化が始まった状態 - 常時点灯
(4) 初期化が終わり、WiMAX を検索している状態 - 消灯
(5) WiMAX に接続できた場合 - 通信があると点滅、無ければ消灯

初期化中 (4) のようにしばらく常時点灯する期間があります。
URoad-5000 本体の WiMAX 電波ランプは、電波をつかむ (5) の段階になるまで消灯
したままです。

現時点でわかってる状態をまとめてみます。

                UD01SS LED        本体WiMAX LED
---------------------------------------------------------------------
本体電源off        消灯             消灯
本体電源on         消灯             一瞬点灯
本体初期化中       消灯             点灯
無線LAN起動後      消灯             消灯
WiMAX初期化中      常時点灯         消灯
WiMAX検索中        消灯             消灯
WiMAX接続後     通信に応じて点滅  電波強度=色,通信=点滅(圏外=消灯)
圏外から脱出等     常時点灯         消灯
ハングアップ時     常時点灯         直前の状態で固定

これらの挙動はファームアップなどで変わる可能性があります。

ハングアップかどうかの判断は、UD01SS の LED が点灯したままで長時間通信
できない状態が続いた場合です。通常はしばらくたつと LED が消えます。
このとき高い確率で本体の WiMAX LED が点灯しており、色が変化しません。

やはり瞬間的な電波の遮断で UD01SS の初期化がかかったものの、その状態を
URoad-5000 側が正しく認識していないのが原因だと思われます。

移動時以外は長時間稼働しても特に問題は出ていません。
一カ所にとどまった状態だと安定して利用できています。


●まとめ

状況の把握が出来れば対策可能なので、移動中でも十分使えると思います。
小さくて軽量な本体もバッテリー駆動出来る点も良いです。
ただ鞄に入れたまま手放しで待機しておくには、まだ不安かもしれません。
もし本体の方に何らかの問題があるならファームウエアの更新で改善できるでしょうか。



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



今日は非常に好調でした。
昨日の不安定さとは大違いで、電車の車内でもきちんとつながるし使えています。
WiMAX の電波状況は右端の LED。消灯→赤→橙→緑 と変化します。
この変化や強さが、普段 VAIO type P + UD01SS で使っていたときとの記憶と
完全に一致。
切れる場所、つながって使える場所も同じで、切れたあとの再接続も問題なく
普通にブラウズできました。
試した場所は新宿近辺から中央総武各駅停車。

つながらない区間はところどころありますが、全く使えないほどではありません。
先日の報告は訂正させていただきます。申し訳ありません。
移動時につながらなくなった症状の条件はまだ絞り込めていないので、もう少し
使ってからレポートしたいと思います。

uroad5000

電波状況や通信時の点滅を凝視しながらだったので、こんな感じ(↑)の手持ちで
使用していました。
一体型ケースがあってもいい良いんじゃないかと思うくらい軽くて薄い本体です。
ポケットに余裕で入ります。iPhone 3GS と両方合わせて 260g くらい。


充電の仕方はまだよくわかっていません。
AC アダプタをつなぐと電源が入ってしまい、いちいち電源を切らないと通信待機
状態のままになります。無線 LAN も見えます。
また充電中のバッテリーマークはずっと緑色で一晩充電しても変化はなし。
充電完了かどうか区別できないようです。
何時間充電すればよいかも記載されていないので、この辺もあとで問い合わせてみます。

バッテリーマークも電波感度マークも一般的なアイコン形状(↓)だけど
バーの本数は変わりません。色が変わるだけ。

uroad5000

ドラクエの Wi-Fi ショッピングも、PSP まいにちいっしょポータブルのデータ更新も
どこでも出来るようになりました。
時間がかかる更新処理は移動中ではなく電波の安定したところでやった方が良いです。
やはりつながるとそこそこ速くて快適です。
Eye-Fi を試すのを忘れました。バッテリーの持ちなどもまた後日。

いらないケース
・ノート PC で使う場合は直接 USB アダプタを差した方が使いやすいし高速

必要なケース
・ノート PC が 64bit OS の場合
・iPod touch, PSP, DS 等、PC 無しでモバイル通信したい場合

※次回に続く(2009/08/06)

関連エントリ
Windows7 beta で UQ WiMAX
WiMAX ルータ URoad-5000


シンセイコーポレーションのモバイルルータ URoad-5000 購入しました。

URoad-5000

WiMAX の回線をどこでも使えるようにするための無線LAN ルーターです。
同じシンセイコーポレーション製の USB WiMAX アダプタを使うことが出来ます。
ちょうど UQ WiMAX 用のUD01SS を持っていたので、ビックカメラで URoad-5000
のみ購入してきました。


●使えるアダプタ

URoad-5000 は USB タイプの WiMAX アダプタに対応したモバイルルーターなので
単体では何も出来ません。添付の「かんたん説明マニュアル」を見ると使える
アダプタは MW-U2510 だけだそうです。

web サイトの FAQ (よくある質問) を読むと UD01SS, UD03SS, BDSS01,
MW-U2510/DM が使えるとのこと。これら全部 MW-U2510 と同じものだそうです。
実際 UD01SS の USB コネクタの裏側には「モデル名 MW-U2510」と小さい字で
書かれています。


●使い道

今のところ iPhone 等の携帯端末から WiMAX 回線を使うには PC か WiMAX 対応
ルーターが必要になります。ルーターの場合アドホックに対応していない
DS や PSP でも使用できます。
箱にも PSP, NINTENDO DS, iPod touch 対応と書かれていました。

また WiMAX の USB アダプタには 64bit OS 向けドライバがまだありません。
ルータ経由なら Windows7 x64 からも WiMAX つなげられるようになるはずです。


●説明書がない

最初に少々戸惑ったのは本体の取扱説明書がないことです。
無線LAN 設定方法までの手順が書かれた「かんたん設定マニュアル」は入って
いましたが、他に説明書も CD-ROM 等もありません。
バッテリーの入れ方や充電方法(ACアダプタをつなぐと電源が入ってしまう)など、
難しくはないけど、しばらく使ってみないとわからないことがあります。
購入時に店員が念入りに付属品の確認をしていたのはこのためかもしれません。
説明書がない等の問い合わせがあるのではないでしょうか。

製品情報ページには FAQ の pdf が掲載されており、補足の説明はこちらを見る
ことになるようです。


●大きさ

本体はバッテリー内蔵ながら非常に小さく手に持った感じも軽量です。
カタログ値でバッテリー込み 110g。
実測でバッテリーパックが 49g、本体が 57g の合計 106g でした。
重量の半分近くがバッテリーです。
UD01SS は実測 19g だったのであわせて 125g くらい。

uroad5000
↑左端が UD01SS、真ん中が URoad-5000

大きさも iPhone くらいのサイズで予想よりも薄型でした。
WindowsMobile のスマートフォンをもう1つ持ち歩くくらいの感覚です。

ただ USB アダプタ UD01SS を接続すると持ち歩きに不安な形ででっぱります。
ノート PC に直接差した場合と可搬性はあまり変わらないかもしれません。

uroad5000
uroad5000


●利用は簡単

USB WiMAX アダプタ UD01SS は、もともと UQ から購入したもので PC につないで
使用していました。そのまま URoad-5000 に差し込むだけで使えるようです。

サイドの小さいボタンを長押しして電源を入れると、しばらくして接続ランプが
点灯します。

最初から WEP と WPA-PSK(TKIP) 対応の 2種類の SSID が登録されていました。
キーはどちらも同じで本体裏に貼ってあるシールに書かれています。
SSID, KEY, PIN と 3つ番号があるけど使うのは「KEY:~」の方です。

WEP しか使えない DS は URoad-****** の方につなぎます。それ以外は
URoadWPS-****** を使えば大丈夫なはずです。(もしだめなら URoad- の方へ)
つながったら「かんたん設定マニュアル」に従って、ブラウザからセキュリティ
等の設定変更ができるようになります。


●電源投入

まだほとんど使っていないので実際のバッテリー寿命などはまだわかりません。
スペック上は連続使用 3時間だそうです。
AC アダプタのコネクタはサイズは細くて特殊な形状でしたが、アダプタ本体も
割と小さい方でしょう。AC アダプタの電圧は 5V。
バッテリーの電源容量は 3.7V 2200mAh でした。


電源を入れてから WiMAX が利用可能になるまで少々時間がかかります。

(1) Wireless LAN のランプが点灯するまで 25秒
(2) USBアダプタ(UD01SS) の LED がつくまで 40秒
(3) WiMAX の電波状況ランプが点灯して使えるようになるまで 60~70秒

電源を入れてから 1分少々。特に UD01SS が電波をつかんで安定するまで意外に
時間がかかるようです。たまに初期化に失敗するのか UD01SS の再初期化が
何度か行われるようで、その場合は 2分近くかかることもありました。

つながらないときは何度も電源入れ直したりアダプタを抜き差しして
しまいましたが、しばらく待つのが正解のようです。
ちなみに電源入れたあとに UD01SS を差し込んでも大丈夫でした。


●速度

VAIO type P に UD01SS 直差しで 3Mbps 出る場所でテストしました。
URoad-5000 経由では 2Mbps。思ったよりも速度が落ちています。

周囲に無線 LAN 機器が多数あるので、無線 LAN 側が混信している可能性もあります。
チャンネルを手動で変更すると多少上がって 2.3Mbps 程に。
でも主に移動時に使うので自動選択に戻しました。

URoad-5000 は 11b/g/n に対応しています。
VAIO type P (Windows7 RC) のダイアログでは 300Mbps と表示されており、
11n で接続されていたようです。


●接続

とりあえず VAIO type P 、DS lite 、iPhone でつないでみました。
それなりに快適で、これなら iPod touch のままでも良かったんじゃないかと
一瞬思いました。

電源を入れてから鞄にいれて都内の電車内で使ってみました。結果はほぼ全滅です。
駅で停車中たまにつながりますが駅間では切れます。
圏外からの再接続が間に合わないらしく、停車中圏内になってもほとんど安定して
使える状態にはなりませんでした。


また VAIO type P に直差しした場合よりも感度が落ちるのか、以前ノート PC で
ぎりぎり使えていた場所でもなかなか安定しません。
とりあえず通勤時は使えないことが判明。
iPod touch + UQ で十分かも、と思ったのは間違いでした。


●まとめ

優先 LAN 端子はなく無線 LAN のみです。
WiMAX 以外使えない専用機なので WiMAX をしっかり使おう、という人向けです。
小型軽量でバッテリー駆動できるのはメリットですが、移動中は安定しません
外出時どこかに腰を据えて使うことになります。

PC に直接アダプタをさして使うよりも速度は少々落ちます。
通信速度も、圏外からの立ち上がりも若干時間がかかるようになった印象です。
ルーターのオーバーヘッドなのか電波の入りが悪くなるのが原因なのかはわかりません。

利点は PC なしで WiMAX の回線が使えることです。
iPod touch や iPhone で高速なネット接続出来ますし、外出時に DS や PSP を
ネットにつなぐこともできるでしょう。

自分の用途だと使える場所が限られてしまうので、まだ活躍できるかわかりませんが
これからいろいろ使ってみます。

※ 次回に続く (2009/08/05)


関連エントリ
Windows7 beta で UQ WiMAX


こちらで触れたように Multitouch 周りは Windows7 ベータの時と仕様が若干
変わっています。なかなか試せず時間がたってしまいました。
ベータの WM_TOUCH

Multitouch API には 2系統あります。WM_TOUCH と WM_GESTURE です。
WM_TOUCH は複数の点の座標をそのまま送ってくるメッセージ、WM_GESTURE は
ある程度の動作を判断したあとに送られてきます。
両者は共存できないので、あらかじめどちらを使うか切り替えておくことになります。

ベータの時は WM_TOUCHDOWN, WM_TOUCHUP, WM_TOUCHMOVE と 3種類
メッセージがありました。RC 以降は WM_TOUCH 一つにまとめられています。
WM_TOUCH 系は複数のタッチ点の座標を一度に送信してくるため、それぞれ個別に
DOWN/UP する可能性があります。受け取った構造体の TOUCHINPUT には個別に
UP/DOWN/MOVE フラグが立っているので情報としては十分です。
複数の点をまとめて WM_TOUCHDOWN/TOUCHUP/TOUCHMOVE とひとくくりに
なっていたのは GESTURE のような操作を想定していたからかもしれません。

少々不思議なのは個別の UP/DOWN/MOVE は本当に区別できるのかということ。
座標はその瞬間サンプリングされたタッチ点なので厳密には個々のポイントを区別
することができず、連続性を持った値になるとは限りません。
例えば移動中のマルチタッチ点が増えたとして、どの点が新規にタッチされたものか
区別しなければならないということです。
サンプリングレートが十分速くて移動が限られている UI 操作ならそれほど問題
ないのかもしれません。
WM_TOUCH のイベントでは Windows 側で同一点の処理が行われており、
TOUCHINPUT の dwID に識別番号が入っています。
dwID が同じものを見ていくと DOWN ~ MOVE ~ UP の流れを見ることが
できるわけです。

lparam がハンドルなので、GetTouchInputInfo() で詳細を受け取ります。
これもベータ時の HANDLE から HTOUCHINPUT に変更されているようです。

void WM_Touch( UINT mes, WPARAM wparam, LPARAM lparam )
{
    int inputs= LOWORD( wparam );
    TOUCHINPUT  tbuf[ 32 ]; // inputs
    HTOUCHINPUT	hinput= reinterpret_cast( lparam );
    if( GetTouchInputInfo( hinput, inputs, tbuf, sizeof(TOUCHINPUT) ) ){
        TOUCHINPUT*  tp= tbuf;
        for( int i= 0 ; i< inputs && i < 32 ; i++, tp++ ){
            ~
	    tp->dwID // 識別
	    tp->x, tp->y // 座標
	    tp->dwFlags // UP/DOWN/MOVE
	}
    }
    CloseTouchInputHandle( hinput );
}

実際の 2点タッチだとこんな感じです。

0:(34054,41033) ID=3 flag=3a mask=4 ex=0 cx=4881 cy=5874 DOWN INRANGE PRIMARY NOCOALESCE 

0:(34054,41033) ID=3 flag=39 mask=4 ex=0 cx=4881 cy=5874 MVOE INRANGE PRIMARY NOCOALESCE 
1:(46230,31629) ID=4 flag=2a mask=4 ex=0 cx=5162 cy=5057 DOWN INRANGE NOCOALESCE 

0:(34007,40957) ID=3 flag=19 mask=4 ex=0 cx=5179 cy=5057 MVOE INRANGE PRIMARY 
1:(46224,31614) ID=4 flag=09 mask=4 ex=0 cx=4974 cy=5874 MVOE INRANGE 

0:(33966,40880) ID=3 flag=39 mask=4 ex=0 cx=5121 cy=5061 MVOE INRANGE PRIMARY NOCOALESCE 
1:(46212,31600) ID=4 flag=29 mask=4 ex=0 cx=4951 cy=5877 MVOE INRANGE NOCOALESCE 

0:(33796,40627) ID=3 flag=39 mask=4 ex=0 cx=5121 cy=5061 MVOE INRANGE PRIMARY NOCOALESCE 
1:(46330,31582) ID=4 flag=29 mask=4 ex=0 cx=4951 cy=5877 MVOE INRANGE NOCOALESCE 

0:(33632,40378) ID=3 flag=39 mask=4 ex=0 cx=4916 cy=5068 MVOE INRANGE PRIMARY NOCOALESCE 
1:(46447,31563) ID=4 flag=29 mask=4 ex=0 cx=4904 cy=5870 MVOE INRANGE NOCOALESCE 

0:(33082,39298) ID=3 flag=39 mask=4 ex=0 cx=4916 cy=5068 MVOE INRANGE PRIMARY NOCOALESCE 
1:(46757,31680) ID=4 flag=29 mask=4 ex=0 cx=4904 cy=5870 MVOE INRANGE NOCOALESCE 

0:(32531,38217) ID=3 flag=19 mask=4 ex=0 cx=3779 cy=5108 MVOE INRANGE PRIMARY 
1:(47074,31801) ID=4 flag=09 mask=4 ex=0 cx=4464 cy=5870 MVOE INRANGE 

0:(32531,38217) ID=3 flag=34 mask=4 ex=0 cx=3779 cy=5108 UP PRIMARY NOCOALESCE 
1:(47074,31801) ID=4 flag=29 mask=4 ex=0 cx=4464 cy=5870 MVOE INRANGE NOCOALESCE 

0:(47074,31801) ID=4 flag=24 mask=4 ex=0 cx=4464 cy=5870 UP NOCOALESCE 

座標値をピクセルに変換するには TOUCH_COORD_TO_PIXEL()。

HP TouchSmart IQ800 を使っていますが、たまに multi touch が無効になっている
ことがあります。何らかの更新のタイミングで、別のドライバに上書きされて
しまっているようです。ドライバを入れ直すと再び使えるようになります。

nextwindow Windows 7 Touch Screen Driver


関連エントリ
Windows SDK for Windows7 RC と Multitouch / Direct3D 11
Windows7 Multitouch API その(2) WM_GESTURE 系
Windows7 Multitouch API


Direct3D と OpenGL は座標系が違います。
そのためデータの Exporter を作るときは、どのプラットフォームにも対応できるよう
いつも両対応で作っています。

Direct3D から見ると両対応は簡単です。
というのも、Maya や XSI など多くのツールが OpenGL の右手座標系なので、
D3D の左手系に合わせるにはもともと座標系の変換処理が必要だからです。
OpenGL に対応するには反転しなければ良いだけ。

ただ OpenGLES 2.0 の場合ジオメトリもライティングもシェーダーだけで行いますし
演算式も任意です。汎用化された GPU では実装に任されているので、座標系の
違いをあまり意識しなくても良いのかもしれません。

普段使っているライブラリやシェーダーを流用することを考えて、Direct3D の
左手座標系でそのまま使ってみました。いつもの感覚でシェーダーやマトリクスの
セットを行っていたら自然にそうなりました。

OpenGL の場合も Depth Buffer の初期値はデフォルトで 1.0 を設定するようです。
比較関数も初期値は GL_LESS なので D3D と違いはありません。
Matrix の設定もモデルデータもシェーダーも D3D と同じパラメータで良いようです。

唯一変更したのは下記の設定のみ。
デフォルトは GL_CCW なので GL から見るとカリングが逆になります。

glFrontFace( GL_CW );

あとはデータもシェーダーも matrix も D3D 互換で使えます。

シェーダーはまだ GLSL のテストをしている状態です。
アニメーションはまだ未確認。

// vsh
uniform mat4	pmatrix;

attribute vec3	vposition;
attribute vec3	vnormal;
attribute vec2	vtexcoord;

varying vec4	ocolor;
varying vec2	otexcoord;

void main()
{
    gl_Position= pmatrix * vec4( vposition.xyz, 1 );

    vec3  lightdir= vec3( 0.0, 0.0, -1.0 );
    ocolor= vec4( 1.0, 1.0, 1.0, 1.0 ) * clamp( dot( vnormal, lightdir ), 0.0, 1.0 ) + 0.2;
    otexcoord= vtexcoord;
}


// fsh
precision mediump float;
varying	vec4   ocolor;
varying	vec2   otexcoord;

uniform sampler2D   texture0;

void main()
{
    vec4   tcol= texture2D( texture0, otexcoord );
    gl_FragColor= ocolor * tcol;
}


// cpp
struct v_type3 {
    float x, y, z;
};
struct v_type2 {
    float x, y;
};

static const v_type3	vdata_p[]= {
    {	-1.0f,	 1.0f,	0.0f,	},
    {	 1.0f,	 1.0f,	0.0f,	},
    {	-1.0f,	-1.0f,	0.0f,	},
    {	 1.0f,	-1.0f,	0.0f,	},
};

static const v_type3	vdata_n[]= {
    {	0.0f,	0.0f,	-1.0f,	},
    {	0.0f,	0.0f,	-1.0f,	},
    {	0.0f,	0.0f,	-1.0f,	},
    {	0.0f,	0.0f,	-1.0f,	},
};

static const v_type2	vdata_t[]= {
    {	0.0f,	0.0f,	},
    {	1.0f,	0.0f,	},
    {	0.0f,	1.0f,	},
    {	1.0f,	1.0f,	},
};

glVertexAttribPointer( attr_vposition, 3, GL_FLOAT, GL_FALSE, sizeof(v_type3), vdata_p );
glVertexAttribPointer( attr_vnormal, 3, GL_FLOAT, GL_FALSE, sizeof(v_type3), vdata_n );
glVertexAttribPointer( attr_vtexcoord, 2, GL_FLOAT, GL_FALSE, sizeof(v_type2), vdata_t );

glEnableVertexAttribArray( attr_vposition );
glEnableVertexAttribArray( attr_vnormal );
glEnableVertexAttribArray( attr_vtexcoord );

static unsigned short	idata[]= {
    0, 1, 2,
    2, 1, 3,
};

MatrixF	projection;
MatrixF	view;
MatrixF	world;

projection.SetProjection( ScreenWidth, ScreenHeight, 1.0f, 5000.0f, DEGtoRAD( 30.0f ) );
view.SetIdentity();
view.Translation( 0.0f, 0.0f, 20.0f );
world.SetIdentity();
world.RotationY( rotang );

MatrixF	mat;
mat.Mul( projection, view );
mat.Mul( world );

glUniformMatrix4fv( attr_pmatrix, 1, GL_FALSE, &mat._11 );
glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, idata );


関連エントリ
OpenGLES2.0 シェーダー管理


OpenGL 系はほとんど使っていないので、余りよくわかっていないことが多いです。
とりあえずシェーダーのバリエーション管理をどうするか。

Direct3D11 では D3DCompile() 命令にプリプロセッサ用のデータを渡すことが
できます。シェーダーコンパイラは Direct3D11 より中立化しました。
D3DCompiler.h / d3dcompiler.lib と独立したモジュールになっており、
FeatureLevel や D3D のナンバーと関係なく共通で使用します。


include に対応するためには ID3DInclude を定義します。
define シンボルを渡す場合は、D3D_SHADER_MACRO 構造体の配列を作っておきます。
(Direct3D 10 Shader4.0 シェーダーのコンパイル)

Shader の機能わけのために、これまでプリプロセッサ命令を活用してきました。
同じシェーダープログラムでもシンボル定義に応じて複数の機能に対応させます。

D3D_SHADER_MACRO  deflist[]= {
{  "SF_ANIMATION",  "1", },
{  NULL, NULL },
};

例えば上記の配列を D3DCompiler() に渡すと、hlsl 上では SF_ANIMATION の定義が
"1" になります。

シェーダー (hlsl) 上では

#if SF_ANIMATION
  ~
#endif

といった感じでコンパイル時の機能指定に応じてコードを分けることができます。

OpenGLES の GLSL コンパイラの場合、特にプリプロセッサ用の定義データを
そのまま渡すわけではなさそうです。
glShaderSource() では、一度のコンパイルでも複数のソースを同時に渡すことができます。
このうち 1つをプログラム内で生成したテキストに割り当てます。
例えばシェーダーコンパイル時に

#define SF_ANIMATION  1

といったテキストを生成してソースの 1つとして与えることで、D3D_SHADER_MACRO
と同じような使い方ができました。


OpenGLES2.0 はシェーダーのみと割り切ったおかげで無駄のないすっきりした
構成になっているようです。
シェーダーの世代的には ShaderModel3.0 相当と思われます。

Direct3D9 + ShaderModel3.0 の場合も、多くの機能がシェーダーで汎用化された
ために従来固定機能だったパイプラインの機能がいくつか使えなくなっています。
それでも API 的には重複する固定機能の設定が残っていたため、どちらでも動くのか
どうか、使えるのかどうかなどわかりにくい点がありました。

この問題は Direct3D10 でいったんリセットすることで解消しています。
Direct3D 10 以降はシェーダーのみと割り切って、重複する固定機能の多くを
排除しました。

そういう意味では OpenGLES2.0 は、Direct3D9 + ShaderModel3.0 というよりも、
Direct3D10 + ShaderModel4_0_Level_9_3 の方により近いといえるかもしれません。


関連エントリ
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D 10 Shader4.0 シェーダーのコンパイル