GLシリーズにおける描画速度の高速化について

29 views
Skip to first unread message

Tatumakigen

unread,
Nov 23, 2011, 10:43:01 AM11/23/11
to Tatumak...@googlegroups.com

なでしこの実行速度はCPUの性能・分解能に非常に大きく依存します。
また、なでしこの実行はシングルスレッドでしか行われないため、マルチスレッドCPUの恩恵を受けられません。
さらに、最近のIntel Core iシリーズのCPUでは低負荷時クロックダウンでの運用がされますが、
なでしこで60FPSゲームを実行した際にクロックアップが行われず処理落ち(30fps程度まで落ちる)する現象が確認されています。

具体的な描画速度の向上方法は、スレッドを分けることであると思います。
例えば弾幕系であれば、まずダブルバッファリング同様、裏画面を複数(4つぐらい?)用意し、MAPや背景、敵&自機、弾&自機弾・・・の様にバラバラに描画したあと、
最後に一枚に描画する方法です。
この場合、描画面積の多い背景画像等をひとつのスレッドに割り当て、
同様に計算から描画まで全ての弾をひとつのスレッドに割り当てて実行することになります。

つまり、
  1. 初期化
  2. 入力系
  3. 座標系の計算(ここでLUAを用いてもよいが、オブジェクト指向のデータが渡せない問題有り)
  4. 描画系 (ここまでスレッドに分割する)
  5. 待機時間 &スレッドの終了と回収
という流れになります。
問題はLuaの呼び出しに少なからず時間が掛かるという点です。
また、なでしこ側の変数はLUAからあえて参照してLUA側で変数を毎回用意する必要があるため、実はなでしこのオブジェクト指向とかなり相性が悪いです。
GLシリーズは、基本的にオブジェクト指向が前提なため、ここの連携が大きな課題となります。
60FPSを目標とするゲームでは、実際のところ初期化と描画待機時間(プロシージャ不応答による描画スキップ回避)で2~3msec損しています。
そのため実際は1フレームあたりの余裕は14msec程度しか存在せず、1000を超えるオブジェクトの管理においては1つの要素の計算あたり14μsec程度しか掛けられません。
加えてGLの回転用の演算などは一回で6μsecあたり必要とし、さらに透過のために転送処理を2度行う必要があります。

なでしこの演算速度はCPUに寄りにけりですが、使い物にならないほど遅いというわけではなく、必要な速度は出ていると思います。
工夫次第でなんとかなるところはいくらかあると思いますし、絶望的な程余裕がないかといわれればそうでもありません。
ただ留意すべきなのは、イマドキのゲームを作るには厳しい ということです。
なでしこで作れるゲームというのは良くてGBAレベルです。
解像度も高くないし、表現力もドット絵に頼ることが多いわけです。

GLシリーズというのはそういうプログラミングよりもゲームを作りたい人向けのツールなので、どうしても複雑化することができません。
従って、スレッド化等は可能な限りユーザーが意識する必要なく作り込む必要があります。

ということで、今後スレッド化について長く調べていこうと思います。
何かアイディアや情報があれば提供お願いします。




・おまけ

Luaでベジェ曲線の点を出す関数を即席で作ってみた。
実際に1000回ほど試して比べてみると差が結構出てしまう。
結果はLua惨敗、最適化等は行なっていないため、他にもっと良いやり方があるとは思われるが、この時点では実用的ではない。


//Lua版
●LUA_Bezier(p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y,t,{参照渡し}xx,{参照渡し}yy)
xとは整数
yとは整数
`
t=nako_get("t")
p1x=nako_get("p1x")
p1y=nako_get("p1y")
p2x=nako_get("p2x")
p2y=nako_get("p2y")
p3x=nako_get("p3x")
p3y=nako_get("p3y")
p4x=nako_get("p4x")
p4y=nako_get("p4y")
lx=p1x+(p2x-p1x)*t
ly=p1y+(p2y-p1y)*t
mx=p2x+(p3x-p2x)*t
my=p2y+(p3y-p2y)*t
nx=p3x+(p4x-p3x)*t
ny=p3y+(p4y-p3y)*t
sx = lx + (mx - lx)*t
sy = ly + (my - ly)*t
tx = mx + (nx - mx)*t
ty = my + (ny - my)*t
x  = sx + (tx - sx)*t
y  = sy + (ty - sy)*t
nako_set("x",x)
nako_set("y",y)
`をLUAする
xx=x
yy=y

//ベジェ曲線演算関数
●Bezier({配列}p1,{配列}p2,{配列}p3,{配列}p4,t,{参照渡し}x,{参照渡し}y)
lxとは実数;lyとは実数;mxとは実数;myとは実数;nxとは実数;nyとは実数;sxとは実数;syとは実数;txとは実数;tyとは実数;
lx=p1[0]+(p2[0]-p1[0])*t;
ly=p1[1]+(p2[1]-p1[1])*t;
mx=p2[0]+(p3[0]-p2[0])*t;
my=p2[1]+(p3[1]-p2[1])*t;
nx=p3[0]+(p4[0]-p3[0])*t;
ny=p3[1]+(p4[1]-p3[1])*t;
sx=lx+(mx-lx)*t;
sy=ly+(my-ly)*t;
tx=mx+(nx-mx)*t;
ty=my+(ny-my)*t;
x=sx+(tx-sx)*t;
y=sy+(ty-sy)*t;
戻る


ちび

unread,
Nov 24, 2011, 6:30:17 AM11/24/11
to なでしこ ゲーム開発研究所
マルチスレッドがどのようなものか自分は存じ上げませんが、並列処理と仮定して考えるならば、純粋なマルチスレッド処理はなでしことLuaどちらでも不
可能だと私は思っています

双方共にそういう言語である以上、並列処理に関する事項は開発元からの技術提供がなければ道を模索することさえ不可能であると思います

しかしながらLuaは、コルーチンとよばれる関数群を抱え擬似的な並列処理を可能にしています

あらかじめコルーチン関数に登録しておく必要はありますがユーザー定義の関数内部でコルーチン関数を呼び出せばいつでも中断でき、また関数外部でコルー
チン関数を呼び出せばいつでも関数を再開できます

これは処理速度に関連する事項に絶大なパワーを発揮するのではないかと考えますがいかがでしょうか?

Tatumakigen

unread,
Nov 24, 2011, 6:56:43 AM11/24/11
to Tatumak...@googlegroups.com
コルーチンという概念にこれまで触れなかったため具体的な恩恵の程がよく分かりません。
各方面では並列処理よりも記述が見やすく簡単になると謳われているようです。

コルーチンとしての並列処理そのものは見通しの良さを考慮しなければなでしこでもステートメントにより同様の処理が可能と思われます。
マルチスレッドと並列処理の高速化という観点における差異は以下のように考えられます。

  • コルーチンを利用した並列処理の場合、変数を使うこと無くループ回数のステートメントのみで遷移処理させることが出来る。
  • マルチスレッドの場合、繰り返し文そのものをCPUのスレッドに分割して割り当て、グローバル・ローカル変数に依存しない独立した処理を高速に終わらせることが出来る。

nVIDIAのCUDAもこのマルチスレッドと同じ働きをしているため、単純で膨大な演算を数百のスレッドに割り当てて刹那に終わらせることができます。

マルチスレッドと並列処理は響きは似ていますが、実際は全く違う概念です。
結局のところ、コルーチンを利用した場合でもサブルーチンは一連の順番を守って処理を進める必要があるため、メインルーチンの処理の順番を自由に変えることができるだけであって高速化の効果については疑問が残ります。
また、やはりなでしことLUAの連携が良いとは言えど、あえてLUAに一任するだけのメリットがコストに負けている気もします。
なでしこからLUAの呼び出しは極力断続的ではなく、連続的な処理を任せたほうがロスが少ない(逆に断続的の場合はロスが増えることが確認されている)ため、
その大きな範囲をLUAで書いてしまうとなると、オブジェクト指向とのギャップの埋め合わせコストが発生します。

また、LUAに任せる中で大きな問題点は、なでしこの変数とLUAの変数が事実上独立しているということです。
LUAを呼び出す中で、なでしこ内の変数はLUAからなでしこAPIを用いてnako_getによって明示的にLUAの変数に格納する必要があります。
オブジェクト指向によってそれぞれが持つ必要な変数をLUAで一つ一つnako_get関数を使っていくとなると、オブジェクトが数百を超えた場合に膨大にメモリ上をやり取りする必要が出てきてしまいます。
この処理が如実に実行速度に響くため、やはり断続的な処理には向きません。
また、nako_getは純粋なグローバル、もしくはローカル変数にしか使えないため、オブジェクトの変数を敢えてLUAで扱う場合はなでしこ側の別の変数に退避させる必要があります。
LUAのGC機能により変数宣言等は必要ありませんが、やたら長い処理の割に受けられる恩恵は微量か、なでしこでの単純な実行よりも時間が掛かる恐れがあります。

ついでに、グローバルメモリやパイプ等WinAPIを利用した変数の共有方法もありますが、こちらもコストの割に見込める高速化は微妙といったところです。


また、そもそもGLシリーズはなでしこをベースとして、なでしこの文法によりゲームが作れることを目標としています。
さらに、実はオブジェクト指向やサブルーチン化を進めることで、初心者のC言語等の別言語への導入を促すために制作されています。


もしLUAを用いて高速化を図る場合は、完全にGLとは差別化を図り、別ライブラリとして提供するべきだと考えます。
ただ、コルーチンという概念は応用できれば実装したい機能でもありますから、今後利用方法を模索していく必要があると思います。
Reply all
Reply to author
Forward
0 new messages