「りも」トップページへ
TIPS index へ

スプライトオブジェの実装

Oct.24.1999
Oct.31.1999 加筆修正

スプライトの概念と実装について。
前回作成したダブルバッファリングの機構にスプライトの概念を 実装してみる実験。同時に X-Server などの動作速度 (ベンチマークっぽいもの)を計測し、どれぐらいの処理が現実的かを 見極める。

[Image]
マーブルマーブルマーブルチョコレート♪

・GTK+ の出番

今回も GTK+ のヴィジェットにあまり頼ってませんので、GTK+ の Tips では なかったりするんですけども。(^^;

一応サンプルではメニューバーとステータスバーを使っているので、 そのあたりは参考になるかもしれません。けれども、結局は Glade で 作って吐き出しただけのものです。(毎度のことですね)

実際に書き込む drawable や、statusbar widgetをトップウィンドウ から得るために、各widgetに名前をつけ、それを RRGetParts なる 関数で抜き出しています。この RRGetParts は自分で作っていながらも なかなか有用であるとご満悦の関数です。(^^
実際ソース内でも結構使われています。


・スプライトの概念とフレームバッファ

ゲームマシンといえばスプライトというくらいにメジャーになった機能 ではありますがそれを Linux や X-Window で実装するにはどうすれば 良いのでしょうか。
スプライトというのは優先順位が付いた小さな絵(セル)が順番に重なって 表示されると言ったものを指します。2Dゲームではこれら小さな一枚一枚が 動いて画面を構成します。ここでは小さなパーツを組み合わせるといった 点に注視します。
それを実装した概念図を以下に示します。

[Image]
Sprite Buffer という、パーツが一枚に収まった Pixmap を SptiteBuffer とここでは呼びます。今回の Sprite の実装とはこの中の一部分を切り出し、 フレームバッファに張り付ける作業の事を指します。つまり、SpriteBuffer の中の絵をシールみたいにぺたぺたとフレームバッファに張り付けて目的 となる画面を作ることを言うのですね。
この手法だとフレーム毎にゼロから描き直さないとならないようですが、 実はそうなのです。毎フレーム、バッファをクリアして必要なパーツを ぺたぺたと転送で張り付け画面を作成する。これがハードウェアスプライトを 持たないビットマップ型ハードにおけるスプライトの概念です。 プレイステーションや Windows の DirectX がそういった形態をとっている 最たる例です。

その形式(ビットマップフレームバッファ型)においてスプライト間の 表示優先順位というのは、どういった順番で描いていったかによります。 つまり、描いた順に重なっていくわけです。 スプライトのバッファを持って、一番奥になるスプライトから順番に 描き重ねていく、これがスプライトの概念です。
今回のサンプルプログラムではそのスプライトの管理および重ね合わせ といった部分をインプリメントしていません。大体どういった形になって いるかだけを捕らえてください。


・X-Window における画像表示

sprite を表示するのには、X-Window の管理する window に書き込まなくては なりません。実際に window に書き込むのはフレームバッファ管理のお仕事 ですので、その上のライブラリが気にすることが無いように実装します。
ここでスプライト表示というのはスプライトバッファからフレームバッファへと 転送する部分を指します。もっと具体的に言うと Pixmap から Pixmap への 転送ということです。この Pixmap 、X-Window が管理しているけど表示されない ためオンメモリバッファと呼ばれることもあります。

Pixmap から Pixmap へと転送するさい GC(GraphicContext) によって その挙動を制限することができます。といっても実際問題はマスク領域を 設定してそのマスク内だけを転送するといった作業に用いるわけですが。
言うところの抜き色を設定する、といったようなものです。残念ながらこの 表示マスク、Bitmapで指定(1bit mask)でしか無いのですが、スプライトを 実装するに当たっては十分使えるものであります。逆に言ったらこれがないと どうすれバインダーといったところなのですが。

このあたりについての詳しいことは SOFTBANK から出ている 「GTK+入門〜基礎から始めるXプログラミング」ISBN4-7973-0871-0 にて解説されていますのでここでは省略します :-P

ていうかサンプルソース見てって感じ。

(追記) 翔泳社から出た
「GTK+/GDKによるLinuxアプリケーション開発」ISBN4-88135-775-1 にもそのあたりが詳しく説明されています。 (詳しすぎてくどいきらいもあり (^^;)


・rr_screen.c における実装

ここでは一般的概念ではなくサンプル内のrr_screen.c においてどのように スプライトを実装しているかを説明します。
以後の説明はサンプルプログラムを覗きながら読んでください。
「おねアクセサリー集」とかを作った経験からインターフェースの実装とか 細部が変更になっているのですが、取り敢えずそのへんは省きます。

当初設計時は、スプライト用の Pixmap も rr_screen に管理させようと 思っていたのですが、色々と考えて行くとそこらは独立していたほうが良い だろうということになり rr_screen ではフレームバッファに描画する部分 のみのインプリメントを行うということになりました。
スプライトを表示するにはスプライトバッファのポインタ、表示位置、 表示サイズ、スプライトバッファ内での位置、を与えてやる必要があるので これを RRSprite という structure にまとめました。RRScreenObjPut には RRSprite のポインタを渡します。

struct _RRSprite
{
  gboolean  sw;              /* 表示スイッチ */
  GdkPixmap  *objmap;        /* 表示するObjのマップ */
  GdkBitmap  *objmask;       /* Objの抜き */
  gint  x, y;                /* スクリーンへの表示位置 */
  gint  w, h;                /* 表示したいObjのサイズ */
  gint  ox, oy;              /* objmap内でのテクスチャー座標 */
  gint  offset_x, offset_y;  /* オブジェ中心の座標オフセット */
};

スプライトバッファの管理、優先順位のマネージメントに関しては rr_screen でなくもう一つ上のレベルで行うことにしたのですが今回の サンプルでは必要なかったので実装していません。また、この部分は タイトルによっては簡素化できたり別形態での実装とある可能性も あるので今回は特に言及しないでおきます。

話は戻りますが、現在インプリメントされているのはスプライトバッファの どの部分からどのサイズをフレームバッファのどこに転送するかといった 部分のみです。優先順位を「誰がマネジメントするか」といった部分を 置いておけば、とりあえずは問題がないと思います。(^^;


・gtk_timeout_add と実時間

今回のプログラムにて秒単位でのフレーム表示枚数を計測するまでは 気が付かなかったのですが、gtk_timeout_add にて指定した時間ってのは 厳密でないのですね。いや、書き方が悪いですね、正確には gtk_timeout_add にて指定した時間ってのがミッションクリティカルな 割り込み時間でなく、gtk_main 内での処理無し時間がそれくらい経過 したらコールされるといったイメージの様です。(このへんソース覗いて いないので言及できませんが)
例えば n msec (ミリ秒)の間隔で hogehoge() という関数をコールするように gtk_timeout_add に設定したとすると、毎回きっかり n msec 毎に コールされるのではなく (gtk_main()が諸処理を行うのに要する時間+n) msec 毎にコールされる感じ。
だから今回のサンプルで 15Frame/Sec に設定しても実際の動作を計測すると 11〜12Frame/Sec ほどの実行値しか現れないのですね。どんなに処理を軽く してもその数値。もちろん、X Server がついてこなかったり、プロセスが 手一杯で遅くなるってのは別の問題ですが、そうでなくてもだいぶ落ちる 数値を出しています。

そんな状況ではアクションゲームを作ったりするのは難しいなあとか 思ってしまいます。もちろん、pthread を用いてもうちょっとタイミング クリティカルに近づける方法もあると思いますけど、そこまでする必要が あるかどうかというのはアプリケーションによると思いますね。
あと、gtk関数内で納めているとポータビリティー(移植性)が向上すると いうのもありますし。(結構重要か?)
取りあえず私が作ろうとしているものに関しては gtk_timeout_add レベルで 問題ないというかその範囲で作ればよいので、現状維持といった感じです。


・サーバークライアント型 Window System

今回のサンプルを見て「やっぱり遅いなあ」と思われたでしょうか、 それとも「意外と動いているじゃん」と思われたでしょうか。
先のスプライトの解説で、オフラインスクリーンである Pixmap を持つ のと同時にスプライトも Pixmap に持つという部分がありました。 このあたりは X-Window の仕組みに関係します。
いまさら私が解説するほどの事でもないのですが、X-Window(X11)は サーバー・クライアント型の Window System です。X Server が画面に ウィンドウやその中身を表示し、X crient がそのサーバーに 描画リクエストをリクエストして画面に表示を行う。ですから、 X-Window ではネットワーク越しに表示するサーバーを選べたり、 クライアントが別のマシンにいても大丈夫なわけです。
なかなかに美しいシステムなのですが、クライアントがサーバーに リクエストをするという形式は大きなオーバーヘッドを生みます。 特にたくさんのデータを扱うときとか、ビットマップの様な大きなデータ を扱うといった場面に顕著に現れます。例えば、ネットワーク越しの クライアントがGIMPを実行したならば?識者ならわかると思いますが、 GIMPはクライアントメモリー上でイメージ操作を行い表示はその度に X Server へとビットマップを送りつけます。ネットワーク環境にも よりますが、10Base Ether とかの上でやると帯域の関係で、GIMPの 表示が目に見えるくらいの遅さになります (^^;

ですから、今回のサンプルは画像データを最初のうちにすべてサーバーに 送りつけ(Pixmapとして持ち)、あとは Pixmap 間の画像操作のみにして いるわけです。クライアント側でフレームバッファは持っていません。
例えばの話、今回のサンプルを LAN越しに実行したりしてもあんまり 実行速度は変わらず、X-Server の速度のみに依存します。さすがに ネットワークの帯域があまりにも細くなっていたら実行値はさがるで しょうけれども。


というわけで、今回のプログラム サンプルソースアーカイブ です。(Size = 52KB)

配布条件はソース/データ共に GPL に準ず。

厳重注意!!
起動したあと色々な数値を設定して動作速度を見ることができますが、 フレームレートとかあまり高くしすぎるとマシンが付いてこなくて 非常に重くなりますので注意してください。場合によっては終了するのが 困難なほど重くなりますので。


rero2@yuumu.rim.or.jp