月別アーカイブ: 2009年9月

iPhone アプリが瞬間的に落ちる心地よさ

iPhone 3GS 使っていて久しぶりに Safari が落ちました。
iPhone の偉いところは、一切ためらわずにアプリが即時落ちること。
この潔さは心地よいとすらいえます。

これが WindowsMobile や PocketPC だと…

・なんだかアプリが重くなってくる
・反応が鈍いなと思いつつ粘ろうとする
・しばらく円グラフがぐるぐる回って操作できなくなる
・さらにしばらく待って完全に固まったことを確認
・諦めてリセットボタンを押す
・起動するまでじっと待つ。1分以上かかる。

この間、実に数分間を無駄にすることになります。

iPhone では問題が発生したアプリが即時終了してくれるおかげで

・待たせない

  ユーザーが操作を選択できる状態に戻ります。
  すぐに同じアプリを立ち上げてページを開き直すことも出来ます。
  また落ちる可能性はありますが、不安定な状態で我慢して使い続けるよりは
  起動し直した方がましなはずです。
  もしくは、別の同等のアプリに切り替えるという選択も出来ます。
  何らかの対策ができるということ。

・システムを巻き込まない

  とりあえずすぐに電話受けられる状態に戻ってくれます。
  無限ループのようなリセットしなければならない状態は回避されます。

もちろん落ちないのが一番良いわけで根本的な解決はなっていませんが、
リセットするくらいの困った状況はほとんど無くなりました。

回復不可能なのに下手にエラーメッセージが出て待たせたり、固まったりするのは
一番避けるべきことだったのだと思い知らされました。

NetWalker PC-Z1

SHARP から新しい端末 NetWalker PC-Z1 が出ます。
小型のノート PC の外観とスマートフォン向け CPU を組み合わせたもの。
かつて似たようなデバイスがあったことを思い出します。

ARM や MIPS など組み込み向けの CPU を搭載し、小型でバッテリー寿命長くて
キーボードを搭載した Handheld PC。WindowsCE H/PC Pro や H/PC 2000 のことです。
ポケットに入る小型なものから 9インチ 800×600 の液晶を持ったノート PC クラスまで、
結構いろんなタイプのハードが揃っていました。
普通の PC との違いは HDD が無く軽量の専用 OS を載せていること。
タッチを用いるなど操作性も違うし、動くソフトウエアも限られるけど、
普段からサスペンド状態ですぐ電源が入るなどそれなりに使う意味がありました。

今回の NetWalker が昔の WindowsCE を搭載した H/PC と違うのは
一般の Desktop / Notebook PC 向けにリリースされている Linux 環境を乗せていることです。
Linux ディストリビューションの一つ Ubuntu をそのまま採用しています。

実際ハード的にも ARM Cortex-A8 800MHz に RAM 512MB + SSD。
最初の Netbook もこれくらいの中身だったし、スペック的にも本当に Netbook に
近いことになります。

・初期の Netbook と同等のスペックを持つ
・デスクトップ向けの汎用 OS を使っている

これらが昔のハンドヘルド PC との大きな違いといえるでしょうか。
そこで今回はあえて NetWalker を ARM を使ったノート PC であると定義することにします。

ここで問題となるのは、Atom を使った 小型の PC とほとんど変わらなくなってくるということ。
例えば同じ Linux を動かすにしても、x86 の方が選択肢が多いし情報も多いし、ハードや
ソフトの対応状況を考えても有利ではないかと言うことです。x86 の小型の UMPC とか
Netbook とかに Linux を入れてもほぼ同じように使えるのではないかと考えられます。

それじゃ NetWalker の立場はどうなるのかといえば、それはおそらく
日本のメーカーがはじめて Linux (Ubuntu) だけ に対応した PC を売ろうとしている事実。
(すでに過去に存在していたらごめんなさい)

選択肢として Linux も選べるのではなく最初から Ubuntu だけ。
家電や携帯電話のように内部で利用しているわけではなく、デスクトップとして載せています。
x86 でないからこそできた決断だし x86 だったらメーカーもユーザーもここまで
割り切れなかったのではないでしょうか。

ARM シリーズの性能向上と Atom の省電力化により使われ方も近づいています。
そんな中、ARM を使った PC の特徴となり得るのは Windows を入れたくならないこと
ではないかと、NetWalker を見て思いました。

今後どのような展開になるか興味あるところです。

WindowsMobile touchkeysip v1.11

いくつか複数の問い合わせが重なったので更新してみました。
使用期間が短いため問題ある場合は引き続き旧バージョンをお使いください。

touchkeysip v1.11 ソフトウエア入力パネル

●複数の sip 登録

オプション画面を拡張しました。オプション画面の開き方は次の通り。

  スタートメニューから
    設定 → 入力 → 入力方法: touchkeysip → オプション

右上のプルダウンメニューで「Sip 0」~「Sip 2」の切り替えが出来ます。
3 種類それぞれに対して個別に

 ・Script Path / 割り当てるスクリプトファイル名
 ・Enable / 有効・無効 切り替え

の設定が出来ます。有効にすると Sip 一覧に touchkeysip 1, 2 のエントリが表示
され、それぞれが独立した Sip のように振る舞います。

touchkeysip111_01.jpg
↑EM・ONE S01SH (WM6) の場合

touchkeysip111_02.jpg
↑Touch Diamond (S21HT) の場合

複数有効にしてもインスタンスは共有されるので、追加のメモリ消費はほとんど
無いはずです。現在選択している SIP 以外のりソースは解放されるので、
メモリに読み込まれているスクリプトも常に 1つだけです。

これで複数のキーボードデータを使い分けることが出来ると思います。
レジストリへ登録する CLSID を増やすだけなので、原理的にはいくらでも増やすことが出来ます。

●回転時の動作

T-01A で画面回転時に位置が不定になるとの報告をいただきました。
SIP を閉じた状態で画面を回転すると、センタリング位置が不定になる問題は修正
しました。

ただし、T-01A で発生している症状は他の機種では再現しないため、根本的に直って
いるかどうかはわかりません。現物がないと原因を特定できないかもしれません。
WindowsMobile 6.x ではタッチ用に UI が変更またはカスタマイズされており、
細かい動作が端末によって異なっている可能性があります。

●消費メモリの軽減

画像読み込み時のみ必要な dll はすぐに解放するようにしました。
bmp 指定時は dll を使わずに古いローダーを用いるように変更しました。

●パネル

スクリプトでパネルを切り替える方法について質問をいただきました。
以下その説明です。

画面(パネル)切り替えという概念は、script の機能を用いて仮想的に実現しています。
各パネルの機能を作るには下記のデータが必要となります。

・ディスプレイリスト
・イベントテーブル

スクリプトでは、この 2つデータを切り替えることでパネルを変更しています。

◎画像データ

パネルの絵は LoadBitmap コマンドで読み込み、必要なタイミングで描画しています。
描画は自分で行う場合と、ウィンドウシステムが必要なときに勝手に描画する場合が
あります。

例えば上に重なった別のウィンドウを閉じたとき、下のウィンドウの絵を復元しなければ
なりません。
このように再描画が必要になった場合に、どのデータを画面に描画するのかあらかじめ
登録しておくのが「ディスプレイリスト」です。

◎ディスプレイリストの登録

SetDisplayList 命令を使います。
画像の転送元や転送範囲、転送先の座標を登録するだけです。
複数登録しておくと順番に描画します。

SetWindowDisplayList 命令を使って、ウィンドウ毎にディスプレイリストを割り付ける
ことが出来ます。

◎直接描画

ディスプレイリストを登録してもすぐには画面が書き換わりません。
再描画が必要なタイミングにならないと呼び出されないからです。

アニメーションの表現や、パネル切り替えなどで即座に画面を書き換えたい場合は
直接描画する命令を使います。
DrawDisplayList は、指定したディスプレイリストをその場で直接描画する命令です。

◎描画のまとめ

ディスプレリストとは描画手順のこと。

SetDisplayList 命令で登録しておくことができます。

描画手順は二通り。この二つを状況に応じて使い分けることになります。

・ディスプレイリストをウィンドウに割り当てる SetWindowDisplayList
  再描画が必要になったら勝手に呼び出して描画してくれます。
  元の描画に戻す手順です。登録しておかないと再描画で元に戻らなくなります。

・その場で画面に書き込みたいなら DrawDisplayList
  すぐ描画します。一度限りなので、他のウィンドウで消されても戻りません。
  アニメーションや、パネル切り替えなどその場で更新したい場合に使います。

◎イベントテーブル

どの座標をタッチしたらどの関数を呼び出すのか、このようなアクションを登録して
おけるのがイベントテーブルです。

talbe <名前>
dataw <ボタンの個数>
dataw EVENT_DOWN  0 16  32  32  FuncPushA  0  0
~
endtable

テーブルは座標範囲と呼び出す関数名、関数に渡す引数で構成されています。
これをボタンの数だけ並べておけば、タッチしたときに任意のスクリプトが走ります。

実際の動作は関数依存なので、任意の文字を送信してもいいし、パネル切り替えなどの
機能を作ることも出来ます。

イベントテーブルは複数作っておくことが出来るので、パネル毎に設定し直すだけで
動作を好きなように変えられるわけです。

テーブルはウィンドウ毎に指定可能で SetEventTable 命令を使います。
SetEventTable には一度に複数のテーブルを与えることが出来ます。
複数のパネルで同じように使うボタンがあれば、イベントテーブルを共通化しておくことが出来ます。

◎ウィンドウ

CommandManual.txt にも説明がありますが、ウィンドウという概念を持っています。
メインパネルはウィンドウの 0 番 (= WIN_MAIN) に相当します。

サブウィンドウを開くことが出来るので、例えばポップアップ小さい画面を重ねて
情報を出すような表現を作ることが出来ます。

WindowsMobile に最初から入っている SIP はメインパネルだけで出来ています。
touchkeysip も複雑なことをしなければ WIN_MAIN だけで sip として機能します。

◎パネルを切り替える方法

ほぼ下記の命令をセットで用いています。
サンプルスクリプトなどで探してみてください。

SetDisplayList ~       # 描画手順の変更、再登録や書き換えなど
SetWindowDisplayList ~ # ウィンドウに DisplayList を割り当て(変更無ければ不要)
DrawDisplayList ~      # その場で画面を書き換えて表示を更新
SetEventTable ~        # ボタンを押したときの動作を変更する

現在選択しているパネルの状態は、適当なグローバル変数を使って管理しています。

関連エントリ
WindowsMobile touchkeysip v1.10 加速センサーで文字入力

OpenGL のはじめ方 (2) wgl

Windows で OpenGL を使う方法がだんだんわかってきました。
DirectX ならとりあえず新しい SDK を入れれば終わりですが、OpenGL の場合は少々違います。
特別な SDK や lib が無くても、ドライバさえ対応していれば使うことができます。

敢えて OpenGL の各種ライブラリは使用していません。便利で容易に任せられる反面、中で何を
やっているのか、最新バージョンを使うにはどうしたらいいのかがわかりにくかったからです。

(1) OS と OpenGL のやりとりは WGL が行う。
(2) Windows SDK には OpenGL 1.1 のヘッダ&lib のみ。最小限。
(3) 新しい OpenGL に対応している GPU ライバをインストールする。
(4) ヘッダファイルは OpenGL Registry からダウンロードできる。
(5) wglGetProcAddress() を使って自分で最新の API を取得する。

API の取得は以前行ったとおり。ここ数日、実際に OpenGL 3.2 で追加された機能を
試すことが出来ました。今度は初期化部分、wgl まわりをもう少し詳しく調べてみます。

MSDN Using OpenGL on Windows NT/2000 and Windows 95/98

●ピクセルフォーマットと初期化

Direct3D と同じように、とりあえず Win32 API でウィンドウを作っておきます。
OpenGL の Context (HGLRC) を作るには HDC が必要です。

そのまま wglCreateContext() を呼び出してもエラーになります。
OpenGL が対応しているピクセルフォーマットになっていないことが原因でした。
あらかじめフレームバッファのフォーマットを指定しておきます。

現在対応しているフォーマットは下記の方法で取得可能です。

HDC hDC= GetDC( hWnd );

// 現在対応しているフォーマットの数を参照する
int format_count= DescribePixelFormat( hDC, 0, 0, NULL );

// 列挙する
for( int fi= 1 ; fi<= format_count ; fi++ ){
    PIXELFORMATDESCRIPTOR   pformat;
    DescribePixelFormat( hDC, fi, sizeof(PIXELFORMATDESCRIPTOR), &pformat );
}

GeForce 190.57 で実際に列挙してみました。126個あります。

GeForce 190.57 PIXELFORMATDESCRIPTOR 一覧

最後の方に W と GL 両方付いているものが Window 描画対応かつ OpenGL で使える
フォーマットです。

MSDN Choosing and Setting a Best-Match Pixel Format の例では
Color=24, Depth=32 を指定していますが、Direct3D を考えても、上の列挙の結果を見ても、
あまり一般的とはいえません。

今回は下記のピクセルフォーマット指定を行いました。
ChoosePixelFormat() を使うとフォーマットのマッチングを自動で行ってくれるようです。

static PIXELFORMATDESCRIPTOR   pformat= {
    sizeof(PIXELFORMATDESCRIPTOR),
    1,
    0
    |PFD_DRAW_TO_WINDOW
    |PFD_SUPPORT_OPENGL
    |PFD_DOUBLEBUFFER
    ,
    PFD_TYPE_RGBA,
    32,     // color
    0, 0,   // R
    0, 0,   // G
    0, 0,   // B
    0, 0,   // A
    0, 0, 0, 0, 0,      // AC R G B A
    24,     // depth
    8,      // stencil
    0,      // aux
    0,      // layertype
    0,  // reserved
    0,  // layermask
    0,  // visiblemask
    0   // damagemask
};

HDC hDC= GetDC( hWnd );

// ピクセルフォーマットの選択
int pfmt= ChoosePixelFormat( hDC, &pformat );
SetPixelFormat( hDC, pfmt, &pformat );

// OpenGL コンテキストの作成
HGLRC hGLRC= wglCreateContext( hDC );
wglMakeCurrent( hDC, hGLRC );

エラー判定を省いていますが、初期化手順は上記の通りです。
hDC, hGLRC は保存しておきます。終了時のコードは下記の通りです。

if( hGLRC ){
    wglMakeCurrent( NULL, NULL );
    wglDeleteContext( hGLRC );
    hGLRC= NULL;
}
if( hDC ){
    ReleaseDC( hWnd, hDC );
    hDC= NULL;
}

●描画ループ

wglMakeCurrent() で hGLRC を指定すれば、以後描画のために gl ~ API を
用いることが出来ます。Double Buffer を指定したので毎フレームの描画の最後に
SwapBuffers() を入れます。Direct3D の Present() に相当します。

    // フレームバッファクリア
    glClearColor( 0.0f, 0.0f, 0.3, 0.0f );
    glClearDepth( 1.0f );
    glClearStencil( 0 );
    glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT );

    // 描画など
    ~

    // flip
    SwapBuffers( hDC );

●新しい Context の作成

基本的な WGL と OpenGL の関係は上記の通りです。

さらに新しい API を使う場合、いくつか追加の手順が必要となります。
まず拡張 API にはいくつか種類があります。

・新しいバージョンの OpenGL core API
・OpenGL 拡張 API
・WGL 拡張 API

これら拡張 API を使う方法はどれも同じです。ヘッダファイルと対応ドライバを手に入れたなら、
あとは wglGetProcAddress() を用いるだけ。
wglGetProcAddress() 関数は、wglCreateContext() のあと wglMakeCurrent() で
hGLRC を設定した後でないと呼び出すことが出来ません。

(1) OpenGL Registry から wglext.h をダウンロードしておきます。
(2) 通常の手段で Context を作成し wglMakeCurrent() しておきます。
(3) WGL の拡張 API 、wglCreateContextAttribsARB() を取り出します。
(4) wglCreateContextAttribsARB() を使って HGLRC を作り直します。

HDC hDC= GetDC( hWnd );

// ピクセルフォーマットの選択
int pfmt= ChoosePixelFormat( hDC, &pformat );
SetPixelFormat( hDC, pfmt, &pformat );

// OpenGL コンテキストの作成
HGLRC hGLRC= wglCreateContext( hDC );
wglMakeCurrent( hDC, hGLRC );

// API 取り出し --- (A)
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB= (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress( "wglCreateContextAttribsARB" );

// 使用する OpenGL のバージョンとプロファイルの指定
static const int  att[]= {
   WGL_CONTEXT_MAJOR_VERSION_ARB,   3,
   WGL_CONTEXT_MINOR_VERSION_ARB,   2,
   WGL_CONTEXT_FLAGS_ARB,           0,
   WGL_CONTEXT_PROFILE_MASK_ARB,    WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
   0,
};

// 新しい HGLRC の作成
HGLRC   hglrc= wglCreateContextAttribsARB( hDC, NULL, att );
wglMakeCurrent( hDC, hglrc );

// 古い HGLRC の削除と置き換え
wglDeleteContext( hGLRC );
hGLRC= hglrc;

// 新しい Core API の取り出し
InitOpenGL();

InitOpenGL() については こちら を参照してください。

なお GeForce で試している分には、特にこの手順を踏まなくても OpenGL 3.2 の命令が
使えるし描画もできています。

上の例では wglCreateContextAttribsARB() だけ個別に取り出していますが、
実際は InitOpenGL() (GLFunction) のテーブルに、gl ~ と同じように wgl 関数
を組み込みました。よって上の (A) のタイミングで InitOpenGL() を用いて
wglCreateContextAttribsARB を含めた一度にすべての拡張関数を取り出しています。

●プロファイルと互換性

OpenGL 3.1~ は DirectX のように、徐々に古い固定機能を無くし新しい機能への
置き換えが進んでいます。それでもアプリケーションが困らないように、従来と互換性を保った
API を使う手段も残されています。

・OpenGL 3.1 + GL_ARB_compatibility
・OpenGL 3.2 + WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB

OpenGL 3.2 では Profile の選択が出来るようになりました。
wglCreateContextAttribsARB() 呼び出し時の WGL_CONTEXT_PROFILE_MASK_ARB
で指定できる Profile は次の 2つ。

・WGL_CONTEXT_CORE_PROFILE_BIT_ARB
・WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB

Core profile はシェーダーを用いた新しい API セット、Compatibility profile は
互換性を持った API です。
テストで GeForce の 190.57 を使っている分には、どちらもほとんど差がありませんでした。

関連エントリ
OpenGL 3.2 GeometryShader をもう少し使ってみる
OpenGL GLSL のバージョン
OpenGL 3.2 の GeometryShader
OpenGL のはじめ方
OpenGL ES 2.0 Emulator
OpenGL ES 2.0
OpenGLES2.0 DDS テクスチャを読み込む
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理

OpenGL 3.2 GeometryShader をもう少し使ってみる

前々回は VertexShaded から GeometryShader へのパラメータ渡しで
built-in のシステム変数 gl_Position を使いました。

VertexShader:
  gl_Position= vec4( POSITION.xyz, 1.0 ) * PView;

GeomteryShader:
  position= gl_in[0].gl_Position;

gl_Position を使うメリットは下記の通りです。

・宣言なしに使うことが出来る
・VertexShader の段階で書き込んでいるので GeomteryShader が無くても描画できる

前々回のような単なるスルー出力では GeomteryShader が無くても描画可能です。
VertexShader で gl_Position に値を書き込んでいたので、GeomteryShader を attach
せずにそのまま描画することが出来ました。

ただし、常に VertexShader の段階で座標が確定するとは限りません。
頂点からの座標値も、普通の変数を介して GeomteryShader に渡すことが出来ます。

Geometry Shader
↑基本の状態

// GLSL 1.5 VertexShader gl_Position を使わない
#version 150

uniform vec4   World[3];
uniform mat4   PView;

in vec3	    POSITION;
in vec3	    NORMAL;
in vec2	    TEXCOORD;

out vec4    vPosition;
out vec3    vNormal;
out vec2    vTexcoord;

void main()
{
    vPosition= vec4( POSITION.xyz, 1 ) * PView;
    vNormal.x= dot( NORMAL.xyz, World[0].xyz );
    vNormal.y= dot( NORMAL.xyz, World[1].xyz );
    vNormal.z= dot( NORMAL.xyz, World[2].xyz );
    vTexcoord= TEXCOORD;
}
// GLSL 1.5 GeometryShader
#version 150

layout(triangles) in;
layout(triangle_strip, max_vertices= 3) out;

in Inputs {
   vec4    vPosition;
   vec3    vNormal;
   vec2    vTexcoord;
} gin[3];

out vec3    vNormal;
out vec2    vTexcoord;

void main()
{
    for( int i= 0 ; i< 3 ; i++ ){
        gl_Position= gin[i].vPosition;
        vNormal= gin[i].vNormal;
        vTexcoord= gin[i].vTexcoord;
        EmitVertex();
    }
    EndPrimitive();
}

gl_in を使用していません。

面毎に法線と texcoord をまとめてフラットシェーディング風にしてみます。(↓)

Geometry Shader

// GeometryShader フラット化
void main()
{
    vNormal= normalize( gin[0].vNormal + gin[1].vNormal + gin[2].vNormal );
    vTexcoord= (gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord)/3;
    for( int i= 0 ; i< 3 ; i++ ){
        gl_Position= gin[i].vPosition;
        EmitVertex();
    }
    EndPrimitive();
}

きちんと法線を算出している訳じゃないので、本来平面のはずの Quad が Triangle に
分かれて見えてしまいます。今回は気にしないでいきます。

さらに座標もずらしてみます。(↓)

Geometry Shader

// GeometryShader 座標もずらす
void main()
{
    vec3 vnormal= normalize(gin[0].vNormal + gin[1].vNormal + gin[2].vNormal);
    vNormal= vnormal;
    vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3;
    vec3    offset= vnormal.xyz*2.0;
    for( int i= 0 ; i< 3 ; i++ ){
        gl_Position= gin[i].vPosition + vec4( offset.xy, 0.0, 0.0 );
        EmitVertex();
    }
    EndPrimitive();
}

上のスクリーンショットではわからないかもしれませんが、投影後の 2D 座標がずれる
だけなので、形状として厳密に正しい物ではないです。
3D で変形を行うには、Geometry Shader で頂点演算を行う必要があります。
Vertex Shader の方がスルー出力になります。

座標値が vec3 になっている点に注意。任意の型やデータをやりとりできるのが
gl_Position を使わない場合の利点です。

なお正しく面法線を求めていれば、三角形ではなく四角形に押し出されていたはず。

3D 座標(local)でずらした結果(↓)
Geometry Shader

// GLSL 1.5 VertexShader
#version 150

in vec3	    POSITION;
in vec3	    NORMAL;
in vec2	    TEXCOORD;

out vec3    vPosition;   // vec4 → vec3
out vec3    vNormal;
out vec2    vTexcoord;

void main()
{
    vPosition= POSITION;
    vNormal= NORMAL;
    vTexcoord= TEXCOORD;
}
// GLSL 1.5 GeometryShader
#version 150

uniform vec4	World[3];
uniform mat4	PView;

layout(triangles) in;
layout(triangle_strip, max_vertices= 3) out;

in Inputs {
   vec3    vPosition;   // vec4 → vec3
   vec3    vNormal;
   vec2    vTexcoord;
} gin[3];

out vec3    vNormal;
out vec2    vTexcoord;

vec4 transform( vec3 pos )
{
    return  vec4( pos, 1 ) * PView;
}

void main()
{
    vec3 vnormal= normalize(gin[0].vNormal + gin[1].vNormal + gin[2].vNormal);
    vNormal.x= dot( vnormal.xyz, World[0].xyz );
    vNormal.y= dot( vnormal.xyz, World[1].xyz );
    vNormal.z= dot( vnormal.xyz, World[2].xyz );
    vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3;
    for( int i= 0 ; i< 3 ; i++ ){
        gl_Position= transform( gin[i].vPosition + vnormal * 1.5 );
        EmitVertex();
    }
    EndPrimitive();
}

以前は VertexShader で受け取っていた uniform 変数 World, PView が
GeometryShader に移動しています。
OpenGL + GLSL の場合、Link したあとに uniform の設定を行うので C++ 側のコード
は何も修正しなくて済みます。結構便利です。

Direct3D だと各シェーダー毎に ConstantBuffer の設定が必要なので、
VSSetConstantBuffers() → GSSetConstantBuffers() と C++ の呼び出し側も
変更しなければなりませんでした。

ここまできたら VertexShader は不要な気もします。
一応試してみましたが VertexShader の省略は出来ませんでした。
Direct3D の Effect (FX) だと VertexShader と GeometryShader に同じ内容を
割り当てることで省略できます。

GeometryShader でポリゴンを増やしてみます。中に元の形状が内部に置いてある状態です。
共有頂点分の演算が重複するので、こういうケースでは VertexShader を併用
した方が効率がよいかもしれません。
out の layout で max_vertices= 6 に注意。

Geometry Shader

// GLSL 1.5 GeometryShader
#version 150

uniform vec4	World[3];
uniform mat4	PView;

layout(triangles) in;
layout(triangle_strip, max_vertices= 6) out;

in Inputs {
   vec3    vPosition;
   vec3    vNormal;
   vec2    vTexcoord;
} gin[3];

out vec3    vNormal;
out vec2    vTexcoord;

vec4 transform( vec3 pos )
{
    return  vec4( pos, 1 ) * PView;
}

void setnormal( vec3 normal )
{
    vNormal.x= dot( normal.xyz, World[0].xyz );
    vNormal.y= dot( normal.xyz, World[1].xyz );
    vNormal.z= dot( normal.xyz, World[2].xyz );
}

void main()
{
    for( int i= 0 ; i< 3 ; i++ ){
        setnormal( gin[i].vNormal );
        vTexcoord= gin[i].vTexcoord;
        gl_Position= transform( gin[i].vPosition );
        EmitVertex();
    }
    EndPrimitive();

    vec3 vnormal= normalize( gin[0].vNormal + gin[1].vNormal + gin[2].vNormal );
    setnormal( vnormal );
    vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3;
    for( int i= 0 ; i< 3 ; i++ ){
        gl_Position= transform( gin[i].vPosition + vnormal * 2.0 );
        EmitVertex();
    }
    EndPrimitive();
}

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