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

フォント表示とダブルバッファリング

Sep.24.1999

GTK+ を用いてフォントを表示する方法についての説明です。
さらにダブルバッファリングの概念と参考プログラムの提示によって、 基本的なゲーム的アプリケーションフレームを提供します。今後の静的ゲーム の作成への足掛かりとなるでしょう。

[Image]
単なるフォント表示サンプルなんですが(笑)

・GLADE の出番

今回は GTK+ のヴィジェットにあまり頼りません。

ウィンドウを一つ作ってそこの全面に drawable を張るだけです。 サイズはあとでソースの方で指定しますので大きさも適当で良いのです。 シグナルマスクとイベントハンドラはちゃんと指定しましょう。指定内容は 前回のポップアップウィンドウと同じです。詳しくは GLADE ファイルを ダウンロードして覗いてください。(と省略する (^^;)

今回の GLADE ファイルです→ font_smp.glade


・ウィンドウサイズを指定する

わりとどうと言うことの無い作業です。

main.c 内でウィンドウを生成したあと gtk_widget_set_usize でメインウィンドウのサイズを指定します。
具体的なソースは

{
  screen = create_screen ();
  gtk_widget_set_usize (screen, 320, 240);
  gtk_widget_show (screen);
}
てな感じです。
screen ってのは GLADE で作成したメインウィンドウ(drawable込み)のこと です。メインウィンドウのサイズを変更すると drawable もそのウィンドウ サイズに従いますので、drawable のサイズを指定する必要はありません。


・フォントを表示するには

フォント表示は X-Window サーバーに依存します。X-Window サーバーが フォントの情報を持ち、描画します、早い話 X-Window が管理する window もしくは pixmap にしか描くことはできません。このことは GDK が持つ Image 構造においてフォントを利用するのは困難であるという事を意味します。 その場合、pixmap 上にフォントを描画して Image に変換という流れに なると思います。
話がちょっとそれました。今回は pixmap を基調とした構造なので特に注意 する必要はありません。

基本的に GDK は XLib ラッパー+α なのですが、フォント部分は割とこの αの部分も多く、手軽に利用できる形態となっています。ありがたいですね。
おおよその流れとしては

といった感じです。

gdk_font_load では 2byte フォントの指定ができませんので、日本語を表示 するなら gdk_fontset_load を利用しなければなりません。ちなみに、 GtkFontSelectionDialog では Fontset の指定まではできませんので、 結果日本語フォントの指定ができません。
故に GLADE で日本語フォントを指定しようとしてやっきになってダイアログと 格闘しても指定することはできないわけです。
現時点での GTK+ の制限ですね。ですから日本語フォントはソース内か リソース(GtkRC 含む)によって指定することになります。

フォントセット指定の具体例です

{
  sfont1 = gdk_fontset_load("-adobe-courier-medium-r-normal-*-10-*-*-*-*-*-*-*,\
                             -misc-fixed-medium-*-normal-*-10-*-*-*-*-*-jisx0208.1983-0");
}
今回は 10dot font を利用しています。もし皆さんの環境で 10dot の日本語フォント がインストールされていない場合はフォント指定部分のソースを書き換えて、 14,16,24 といったサイズのフォントを指定してみてください。サンプルソース内では rr_main.c 内の RRScreenFontInit にてフォントを指定していますのでそこを 書き換えてください。

フォント表示は gdk_draw_string 等を使います、GDKリファレンス等を 調べてみてください。
注意すべき点としては、指定した座標は表示位置において文字列の「左下」の 座標になります。「左上」じゃないんですよぉ。おそらく英文フォントに おいては指定座標がベースラインとなるので指定座標より下に描かれる可能性も あるかもしれません。
それと表示できる日本語のコードは EUC ですのでその点もご注意を。

サンプルソース内で実際に文字列を描画しているコードです。

{
  w = gdk_string_width(sfont1, disp);
  h = gdk_string_height(sfont1, disp);
  gdk_draw_string(pixmap[page], sfont1, win_gc[page],
		  font_x, font_y+h-1, disp);
}


・ダブルバッファとは

アプリケーションはなにかしらのグラフィック等をウィンドウに表示して ユーザーに提示するわけですが、このウィンドウ描画はどうやればよいの でしょうか。
ダイレクトにウィンドウの drawable に書き込むのが最も原始的な手法ですが これでは描き終るまでの過程がユーザーに見えてしまいます。結果描きかけの 不完全な絵を見せてしまうことになりユーザーとしては意図しない画像が 表示されていることになります。また、その過程が見えること自体ユーザーに とっては「ちらついて」見え大変不快なものです。
そこで、メモリー上の見えないフレームバッファ(X-WindowではPixmap)でユーザーに 見えないようにこっそり画像を描いて準備し、描き上がったら一気にウィンドウの drawable に転送するといった手法をとります。これなら、ユーザーに描いている 過程を見られません。ユーザーとしてもちゃんと描き上がった結果だけが見えて いるのでちらついた感もほとんど感じません。
ここまでではシングル(フレーム)バッファでしかありません、これでも十分な ようですがなぜダブルバッファにする必要があるのでしょうか。答えは Expose です。ウィンドウの重なり変更やサーバーの状態変化によって再描画が必要な 場合何をもとにウィンドウ上の drawable を描き直せば良いのでしょうか。 シングルバッファの場合そのバッファの内容が描きかけのときに Expose が 来てしまうと、その描きかけの画像を表示しなければならない事になります。 この状態を回避するために、描画中のバッファと Expose で再描画用のバッファ の二つをフレームバッファとしてメモリー上に持ちます。これがダブルバッファ です。

[Dubble Buffer]
ダブルバッファの概念図

描いている方のフレームバッファの処理が終了(描き終える)したら、その新しい 画像をウィンドウ drawable に転送し、もう片方のフレームバッファと役割を 交替します。で、こんどはさっき表示役だったフレームバッファに次のフレーム (新しい画面)をどんどんと描いて行くのです。


・ダブルバッファのインプリメント

実際にはウィンドウの drawable と同じサイズの Pixmap を2つ用意し、 それをスイッチフラグで役割を割り振るだけです。概念はともかくやっている ことは割と単純ですね。

一応実際にスイッチングしている部分のコードです。

/* -------------------------------------------------------------
  RRScreenSwitch     -- ダブルバッファリング切替え

   return : 現在の書き込み可能バッファ番号(0-1)
   void
-------------------------------------------------------------- */
gint       RRScreenSwitch(void)
{
  /* -- 書き変わった部分だけをアップデート */
  if ((drawmemo_w > 0) && (drawmemo_h > 0)) {
    gdk_gc_set_clip_mask(win_gc[2], NULL);
    gdk_gc_set_clip_origin(win_gc[2], 0, 0);
    gdk_draw_pixmap(canvas->window, win_gc[2], pixmap[page],
		    drawmemo_x, drawmemo_y,
		    drawmemo_x, drawmemo_y, drawmemo_w, drawmemo_h);
  }

  /* -- 次行きましょう */
  page ^= 1;
  drawmemo_x = drawmemo_y = -1;
  drawmemo_w = drawmemo_h = 0;

  /* -- 前の画面をコピーする */
  gdk_gc_set_clip_mask(win_gc[page], NULL);
  gdk_gc_set_clip_origin(win_gc[page], 0, 0);
  gdk_draw_pixmap(pixmap[page], win_gc[page], pixmap[(page^1)], 0, 0,
		  0, 0, screen_w, screen_h);
  return(page);
}
その他ダブルバッファに応じたフォント表示、画面塗りつぶしクリア等、 サンプルソース内の rr_screen.c に軽くまとめてあります。まだまだ、 関数も少なく実用には遠いですけどおおよその概念を把握はできるかと思います。

・フレーム単位の処理

通常ゲーム機のプログラムというのは表示画面の V-Sync, V-Blank 割り込みに 厳密に縛られた構造になっています。コンピューター工学的に言うと、同期型の リアルタイムシステム、といったところですか。この形式の場合 V-Blank まで 空ループして待つとか、V-Sync 割り込みで V-Blank のタイミングを知ります。 V-Blank というのはチューブ(ブラウン管)の走査線(電子ビーム)が下から上へ 戻るタイミングでそのとき走査線が画面を描いていないことからそのタイミングで 画面を書き換えればちらついたり波打ったりしないわけです。 その逆で走査線が走っている途中でわざと絵を書き換える技が ラスタースクロールと呼ばれる走査線多段スクロールだったりします。

さて、X-Window のシステム上ではこの V-Sync を得ることは大変困難です。 (というかできない)だから、一般的なゲームの作り方が成立しないので どうしましょう、といった感じになります。
画面の走査線とぶつかって描画中に揺らいでしまうのは諦めてしまうとして、 一定時間毎に割り込み、もしくは処理を行うにはどうすればよいのでしょうか。
一定時間毎にゲームメインがコールされる(V-Sync割り込みもどき)という 状況をつくるために GTK+ の gtk_timeout_add を用いて適時にゲームメインを たたきます。

{
  /* --- メインルーチンの準備 (timeの単位はms)*/
  gtk_timeout_add(1000/30, RRMain, NULL);
}
ゲームメイン部分をスレッド(もしくはプロセス)として回しておけるときは、 gtk_timeout_add の飛び先でタイミングフラグを立てるだけ、にしておき、 ゲームメインではそのフラグを監視し、立ったら次フレーム(V-Blankタイミング が訪れたというのに近似)へ進むといった形式にします。この方式の場合、 処理がフレームオーバーを起こしても不都合が起きないし対処しやすいという 利点を持ちます。


というわけで、上記の サンプルソースアーカイブ です。(Size = 220KB)

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


rero2@yuumu.rim.or.jp