第6回: 再描画処理とメモリ描画環境

いつものパターンですが、まず前回の問題点をおさらいしましょう。

しかし、上のプログラム、試してみると微妙な問題を抱えています。ずっとそのプログラムのウィンドウ自身が表に出ている時は良いのですが、他のウィンドウの裏に隠れてしまったときに、表側にもう一度ウィンドウを出してくると、ウィンドウの中身が消えてしまいます。また、ウィンドウが画面からはみ出した場合、それをもう一度画面の中に戻しても同じ事が起きます。

今回のプログラム全体を固めたものはこちらです: lecture6.tar.gz

1. 再描画処理

1.1. 再描画処理とは?

上の問題は複数のウィンドウを持つシステムならではの特徴なのですが、他のウィンドウから邪魔されたりすると、ウィンドウ作業領域に描いてある内容が無効になってしまいます。つまり、中身を書き直さなくてはならない状況になります。

そこで、そのようにウィンドウの描いてある内容が無効になってしまった時に、「ウィンドウの中身を書き直して!」というイベントが発生します。そのイベントの事を"再描画イベント"と呼びます。

evt_loop関数で処理させる場合は、前回のプログラムでのdisp_fnにその処理を記述します。(ちなみに、wget_evtで処理させる場合には、EV_REQUEST(要求イベント)の一つ(W_REDISP)として拾ってくる必要があります。)

1.2. 前回の場合

ところで、前回のdisp_fnを抜き出して見てみましょう。

void disp_fn(W wno, W mode, RECT* r)
{
  switch (mode){
  case -1: case 0: case 1: case 3:
  case 5:  case 6: case 7: case 9:
    do {
      if (wsta_dsp(winfo[wno].wid, NULL, NULL) == 0)
        break;
      wera_wnd(winfo[wno].wid, NULL);
      // <--- ここに再描画処理を書く
    } while (wend_dsp(winfo[wno].wid) > 0);
    break;
  default: // その他
    break;
  }
  return;
}

wsta_dspやwend_wndなどの不明な関数群が並んでいますが、これは無効になった部分を調べたり、どの部分が無効かを調べたりするためのものです。とりあえずここでは無視するとして、ここで重要なのは、"// <--- ここに再描画処理を書く"と書いた行です。ここが再描画させる内容の全体です。

で、その部分が空になっていますね。wera_wndはウィンドウの無効になった部分を消すための関数なので、何も書かれていない部分を単純に消去します。従って、その後の部分になにも無いわけですから、ウィンドウの中身が消されるだけの結果に終わります。

再描画処理の実装では、その部分に描画処理を全て記述しなければなりません。ところが、第5回のプログラムのままでは、マウスのボタンが押された位置を記憶していませんから、どこに描画してよいかわかりませんね。ということで、そのようにプログラムを丸ごと書き直せば良いわけです。次節でそれを実際にやってみましょう。

1.3. 再描画処理を"まとも"にしよう

早速、書き直したプログラムを見てください。前回のソースコードと比較することで、大体分かるのではないかと思います。

違いを順に見ていきましょう。まず、pdpress_fnですが、

W pdpress_fn(W wno)
{
  // 直接 gfil_ovl するのではなく、データを更新し、wreq_dsp します
  if (stack_ptr < MAX_CIRCLE - 1){
    stack[stack_ptr] = wevt.s.pos;
    stack_ptr++;
    wreq_dsp(winfo[wno].wid);
  }
  else {
    sig_buz(0);
  }

  return 0;
}

となっています。前のプログラムと違って、直接pdpress_fnの中で描画を行っていないことに注意してください。見て分かるように、stackという配列変数の中にそのときの座標値を記録していっています。

その後のwreq_dspが重要で、これはBTRONのイベントマネージャに対して、再描画イベントを送信するためのシステムコールです。引数として、ウィンドウID、つまり、どのウィンドウの中身が無効になったかを指定します。

その「描画領域が無効になった」というイベントに対して応答するのが、皆さんすでにご存じのdisp_fnにあたるわけです。そちらの方は以下のように書き直します。

void disp_fn(W wno, W mode, RECT* r)
{
  switch (mode){
  case -1: case 0: case 1: case 3:
  case 5:  case 6: case 7: case 9:
    do {
      if (wsta_dsp(winfo[wno].wid, NULL, NULL) == 0)
        break;
      wera_wnd(winfo[wno].wid, NULL);

      // 再描画処理 -- ここで溜め込んでいた全ての表示を行います
      GID gid = wget_gid(winfo[wno].wid);
      PAT pat = {{ 0, 1, 1, 0x100000ff, 0x10ffffff, FILL75 }};
      RECT rect;

      for (int i = 0; i < stack_ptr; i++){
        setrect(rect, stack[i].x - 10, stack[i].y - 10,
                stack[i].x + 10, stack[i].y + 10);
        gfil_ovl(gid, rect, &pat, 0, G_STORE);
      }

    } while (wend_dsp(winfo[wno].wid) > 0);
    break;
  default: // その他
    break;
  }
  return;
}

こちらは分かりやすいと思います。変数stackにあるデータに基づいて、実際に描画を行っているだけですね。どうでしょう、BTRONのイベント処理が分かった気がしませんか?

1.4. まとめ

要点をおさらいしておきましょう。

  1. 基本的に描画処理は disp_fn 以外の部分では行わず、起きたイベントに応じて対応するデータを変更するのがポイント。
  2. データを変更した場合には、wreq_dsp関数を用いて、再描画の必要性をイベントとして通知します。
  3. 再描画イベントを受け取るのは disp_fn。disp_fnでは変更されたデータを用いて実際の描画を行う。
※ ただし、1つ目の項目については例外もあります。wreq_dspを使った場合、再描画は少なくとも次のイベント受信(wget_evt)の時までは待たされることとなります。また、画面全体を書き直すので、少しだけ効率が悪いです。そういった面から、直接disp_fnを呼び出すなどしてしまった方が、実は良かったりもします。

しかし、その方法の場合、更新が反映されない部分などが出たり、更新が不完全になってしまう場合があります。ですから、とりあえず今のところはwreq_dspを使う方針で行って欲しいと思います。分かるようになったらそのときにいろいろ工夫を試みるのがよいと思います。

蛇足ですが、ここまできちんと理解しながら読み進んでいれば、PMCの開発環境に付属のsample1は読めるようになっていると思います。ぜひ試しに読んでみてください。(読めたらこの入門は卒業してもよいかもしれません。)

2. メモリ描画環境を使ってみる

2.1. メモリ描画環境とその特徴

前章で紹介したように、基本的にはデータとして描画すべきものを保持しておいて、disp_fn, wreq_dspの組み合わせで画面への描画を行うわけですが、これではしんどい場合があります。前のように点を描くだけのプログラムなら大したことはありませんが、はるかに複雑な描画を行うアプリケーション(ゲームとか)では、この方法ではちょっとやっていられませんね。

そこで、データとして「メモリ描画環境」というメモリ上の描画環境を用いる方法があります。これは、ビットマップを描画環境として使用してそこに絵を描いておき、そのビットマップを実際の画面(ウィンドウ描画環境)に転送する、という方法を採ることが出来ます。これは、他のOSのプログラミングでも良くやる方法で、"ダブルバッファリング"などとも言われたりします。

この方法の特徴には、以下のような点が挙げられます。

2.2. メモリ描画環境を用いた描画処理

この項目は、まともに説明するのは結構難しいので、とりあえずモジュールとして提供することにします。とりあえず使えるので、中身については今の段階では理解しなくていいです。とりあえず、ソース全体はlecture6-2.tar.gzとしてまとめておきましたので、それを適当に再利用してください。(再頒布等も許可なしに自由に行ってください。)

使い方ですが、いつもの通り、今回のソースでどこが変わったのかを見ながら進めていきましょう。

まず、始めの方の部分ですが、

#include "memgid.h"

....

// メモリ描画環境関連の変数たち
static GID mem_gid;
static BMP mem_bmp; 

のような宣言がなされています。#include "memgid.h"は、…お分かりですね。lecture6-2.tar.gzの中に入っているmemgid.hとmemgid.ccを使用するためのコードです。当たり前ですが、memgid.ccとmemgid.hをsrc/ディレクトリの中に入れること、またMakefileのソースファイル記述の行にmemgid.ccを加えておくことも必要となりますので、注意しましょう。

mem_gid, mem_bmpは今回使うメモリ描画環境の実体を保存しておくための変数です。

ウィンドウを開いたら、以下のような処理を行います。メモリ描画環境を作成しています。create_memgidの定義はmemgid.ccの中に書いてあります。

  // ちらつき防止のため、背景色を「透明」とします
  wset_bgp(winfo[0].wid, &bgpat);

  // メモリ描画環境の作成
  mem_gid = create_memgid(&mem_bmp, r.c.right - r.c.left,
                          r.c.bottom - r.c.top);

ビットマップはメモリを食うので、不要になったら、以下のようにしてメモリ描画環境を解放します。ウィンドウを閉じた後に行うのが、とりあえず適切だと思います。

  // メモリ描画環境の解放
  release_memgid(&mem_bmp);

disp_fnの実装、つまり再描画イベントが発生したときの処理は、驚くほど単純です。単純に保存して置いたビットマップを、ウィンドウ描画環境の上に転送するだけでOKです。

      // 再描画処理 -- メモリ描画環境の中身を転送するだけ
      GID gid = wget_gid(winfo[wno].wid);
      grst_bmp(gid, &(mem_bmp.bounds), &mem_bmp, &(mem_bmp.bounds),
               NULL, G_STORE);

ぐだぐだと書いてきましたが、ここまでの内容は定型的なものです。以降の回でも、とりあえず今回のソースを変更して使って頂いても特に問題ないでしょう。

さて、注目すべきは、pdpress_fnの中身ですが、以下のようになります。

W pdpress_fn(W wno)
{
  RECT rect = {{ wevt.s.pos.x - 10, wevt.s.pos.y - 10,
                 wevt.s.pos.x + 10, wevt.s.pos.y + 10 }};
  PAT pat = {{ 0, 1, 1, 0x100000ff, 0x10ffffff, FILL75 }};

  // メモリ描画環境に書き込んだ後、wreq_dspします
  gfil_ovl(mem_gid, rect, &pat, 0, G_STORE);
  wreq_dsp(winfo[wno].wid);

  return 0;
}

描画環境として、メモリ描画環境であるmem_gidを指定していることに注意してください。不思議ですが、これだけで、ビットマップmem_bmpの中身が書き代わり、ビットマップ上に円が描かれます。それと、gfil_ovlだけではビットマップに絵が描かれただけで、実際の画面上に何も反映されません。なので、反映させるためにwreq_dspで再描画イベントを発生させます。

この処理を見て分かるように、第5回のような感覚でどこでも描画関数を呼び出して、なおかつそれが再描画の時にも反映されている事が分かりますね。便利ですので、デメリットも考慮にいれつつ、ぜひ利用してみてください。

演習問題

ここまでくると、かなりレベルの高いプログラムが読めるようになっていると思います。(もちろん仕様書を調べながら、ですけれども。) ぜひいろいろなソースコードを読んで、知識を深めていってください。

  1. disp_fnの中身を空にする(switch文全体ごと消す)と、何が起こるでしょうか。予想し、それを実験で確認してください。
  2. PMCの開発環境に付属のサンプル、sample1のソースコードを読んでみましょう。
  3. ミニゲーム(たとえばモグラたたきとか)を作ってみましょう。

[ 戻る ]

Last modified: Sun Oct 3 16:39:19 2010
Valid XHTML 1.0!