Windows - 4 Double Buffering [Programming]
どんなプラットフォームであっても、凝った絵を描こうと思ったらプラットフォーム特有のお作法を覚える必要がある。直感的にわかりやすいのはフレームバッファに直接イメージを書き込んでいくような初期の PC にあったような方式だろうけど、これだとテキストを描くとか背景を抜くといった簡単な機能でさえ自分でロジックを記述しなければならないので意外にスキルを求められる。その対極にあるのが 3D のレンダリング。フレームバッファ自体を操作することはほぼ不可能で、与えられた描画機能だけを用いて画面を作り上げていく。画面上に現れる建物や人といったオブジェクトを作成するのにもそれなりのツールやテクニックが必要となるので上記とは別の意味で敷居が高かったりする。
Windows の場合、マルチタスク OS の環境下で動作するアプリケーションは、デバイス(ハードウェア)を抽象化する GDI(Graphics Device Interface) と呼ばれるサブシステムの提供する機能を用いて描画を実行することになる。直接デバイスを扱わないというのは、利点であるとともに欠点となってしまうこともある。ゲームのようにどうしてもデバイスの機能を扱いたいという要望に対しては、Direct X と呼ばれるものを利用していくというのが、当時の Windows の流儀のひとつだった。
今回の話は、フォームやダイアログに既成のコントロールに描画を任せてしまう場合には殆ど関係のない話。
GDI を操作するための最初の関門は、いくつかの用語の意味を覚えるところにあった。その筆頭はデバイスコンテキスト、、、公式の文書はこれ。機械翻訳すると「デバイスコンテキストは、グラフィック・オブジェクトとそれらに関連する属性、出力に影響するグラフィックモードを定義する構造です。」な感じになるのだけれど、、、訳わからん。
意訳というか、ほぼ誤訳に近い感じになってしまうのだけど、「デバイスに依存しない描画機能の集合体」というのがその正体。「直線を引く」とか「図形を塗りつぶす」とか「文字列を書く」といった、描画機能を提供してくれているのがデバイスコンテキストになる。
次が GDI オブジェクト。GDI オブジェクトは、デバイスコンテキストに与えて、描画機能の特性を変更するためのオブジェクト。具体的には、「ペンの太さや色(Pen)」 とか「塗りつぶすパターン(Brush)」とか「文字フォント(Font)」といったものを表すオブジェクトのこと。ビットマップ(Bitmap) も GDI オブジェクトのひとつで、扱いとしてはフレームバッファに近いものだけど、実際は似非な存在。
2つめの関門は、自分の認識と GDI の実装とのギャップの大きさにある。GDI を使用した描画には「自分のタイミングで描画する事ができない」 という特性がある。具体的には、WM_PAINT メッセージに応答する形で描画を実行するという手続きを踏む。そして、ただ待っているだけでは WM_PAINT メッセージはやってこないので、再描画が必要となった領域を無効化することでメッセージを誘発するという手続きも必要になる。
実際の描画はこれらの要件(制約?)の下で実行して行くことになるのだけれど、これだけの情報で実践しても殆どの場合ガッカリするのがオチだったりする。Web でも「描画はできるようになったけど、チラツキが酷い。」とか「チラツキを抑えるためのテクニックを教えて」といった書き込みを結構見かける。
解ってしまえば別に難しいことでもないのだけど、教えてくれるひとが近くにいないと、なかなかゴールにたどり着けないかもしれない。
GDI を使った描画をする際、ダブルバッファリングの手法を用いる事がほぼ必須になる。オフスクリーンレンダリングなどとも呼ばれるこの手法、もともとはモニタの表示期間に描画を実行することを避けることで画面のチラツキを回避しようとするためのもので、直接 V-Sync 信号を扱う初期のパソコンや安価なゲーム機のプログラミングでは常套手段だった。
ハードウェアを直接操作することができない GDI を用いた描画では、ダブルバッファリングによって完全に問題が解決するわけではない。まあ、程度の問題ということになってしまうのだけど、動きのあるゲームのようなものでない限り、問題ないレベルに仕上がる手法ということになる。
GDI がうまいこと処理していてくれていればアプリケーションレベルで対処する必要もないのだけれど、 やってくれないなら自分でやるしかない。そして、定型的な手順になるので何度か実践しているうちに慣れるのも早いだろうし、採用したことによる効果もほぼ問題ないレベルになる。
ダブルバッファリングを用いた描画の手順は以下の通りになる。
- WM_PAINT をメッセージを受けたら描画を開始する。Win32 API を利用する場合は BeginPaint の発行、MFC を利用する場合は CPaintDC の構築を行い デバイスコンテキストを取得する。
- 1. で作成したデバイスコンテキストのコピーを作成する。同様にビットマップを作成して、コピーに選択する。
- 2. で作成したデバイスコンテキストで描画を実行する。
- コピーに作った描画イメージを、1. で取得したデバイスコンテキストに転送する。多くの場合、BitBlt によるイメージの転送になる。
- 描画の終了。Win32 API の場合は、EndPaint を呼び出す。MFC なら、スコープの消滅とともに適当に処理してくれるので、なにも考えなくていい。
ダブルバッファリングを使用しない場合と比べると、2. および 4. の手順が追加になる。チラツキの根本的な回避というわけではなく、要は 3. の処理時間よりも 4. の処理時間の方が圧倒的に短くなるため、結果としてチラツキが目立ちにくくなるだけというものだったりする。
コメント 0