GUIサーバ高速化/04.部分描画実装(libbaygui)/描画作法


Top / GUIサーバ高速化 / 04.部分描画実装(libbaygui) / 描画作法

質問

一般的な作法

  1. 描画イベントは範囲指定を伴う。
    • ほとんどのシステムでは描画イベント自体に範囲指定が伴って通知されます。たとえば.NETのWindows Formsでは、OnPaint(PaintEventArgs e)となっていますが、e.ClipRectangleとして描画範囲が通知されます。
    • 今でもWindowsやX11ではデフォルトの動作は次のようになっています。
      • ウィンドウを動かしたり前面に持って来たことで今まで隠れていた部分が表示されたりした場合、システムからその範囲が通知されて部分描画を要求されます。範囲が複数の場合、その回数だけ描画要求が来ます。
      • アプリケーションがビジー状態(フリーズ)の場合、今まで隠れていた部分が白いままになったりするのは、描画要求に応じることができないためです。
    • 範囲指定に強制力はないため、ついでに別の部分も書き換えるというようなことは可能です。
      • 上にウィンドウが2枚重なって別々の部分が隠されていたものがクリックしたため前面に来たというようなケースでは、範囲指定が伴って描画要求が2回来ます。このような時に毎回全描画していると無駄が生じます。
    • 真面目に部分描画に応じるのは面倒なので、実際には以下のようにします。
      • 簡単なものであれば、気にせず毎回全描画します。ただしその場合でも描画をクリッピングしておくことで、指定範囲以外への描画が無視されるようにしておくのが普通です。そうすれば全描画のときと同じ描画アルゴリズムを使っていても、実際に描画されるのは必要な部分だけとなります。大抵はシステムレベルで描画関数のクリッピングに対応しているため、自前でクリッピングする必要はありません。
      • リストのように範囲ごとにどの項目が割り当てられているか計算できるようなものでは、クリッピングした上で該当項目だけを描画します。
      • 毎回再描画するのが面倒なときはダブルバッファを使います。ダブルバッファの作法は次に項目を立てて説明します。
  2. ダブルバッファではシステムの描画要求は隠蔽される
    • ダブルバッファされていれば描画要求はイメージを部分描画するだけで自動的に対応することができるため、ウィンドウを動かしたり前面に持ってきたりといった内容の変更を伴わないようなケースでは、描画要求が自動処理され隠蔽されます。
      • システムでダブルバッファの面倒を見てもらえない場合は、描画イベントにはバッファからBitBltするコードだけを仕込んでおきます。
    • ダブルバッファでは描画はバッファに行っているため、描画しただけでは画面に反映されません。人為的に描画要求を起こしてBitBltさせます。それがInvalidateです。
      • 具体的な手順として、バッファに対して描画して、その範囲をInvalidateして、描画要求で(自動的に)BitBltすることで部分描画させます。
      • .NETではこのような手順は隠蔽されています。詳細は後述します。

ダブルバッファの作法

直接描画での挙動はどのシステムもほとんど同じですが、ダブルバッファはライブラリ側で受け止めて隠蔽しているため、ライブラリの設計によって違います。

私が今覚えているのは.NET(Windows Forms)とQtだけですが、それについて書きます。BayGUIはAWTの作法に則っているので、本来なら一番重要なのはAWTなのですが、残念ながら私はAWTには詳しくありません。とりあえず項目を立てて軽く調べた範囲内で書きます。

Windows Forms

  1. ダブルバッファはデフォルトではありません。オプションで指定します。
  2. 描画イベントと無関係に描画することができます。
    • キーイベントの処理中に追加された文字を描画するということが可能です。
    • MonaFormsもこの仕様に従っています。
  3. ダブルバッファでの描画イベントは基本的に全描画です。
    • ウィンドウをどかしたなどの部分描画は自動処理されるためOnPaint()が呼ばれません。
    • Refresh()するとバッファがクリアされます。その後でOnPaint()が呼ばれるため、ダブルバッファ時の描画は全描画オンリーと見なせます。
      • MonaFormsではRefresh()してOnPaint()が呼ばれるのは同じですが、バッファはクリアされません。この辺の詰めは甘いです。
  4. ダブルバッファされたウィンドウに対する描画領域を覚えていて、描画がすべて完了した後、自動的にその部分が反映されます。
    • このことは描画中にビジーループを発生させると、描画が完了したものが画面に反映されていないので確認できます。ビジーループを解除すると、描画完了後に一気に反映されます。
    • ひょっとしたら2Dアクセラレーションに頼って全領域をBitBltしているのかもしれません。そこまではちょっと分かりません。あくまで挙動を見て推測しているだけです。
      • 2Dアクセラレーションを前提にした場合、バッファもグラフィックボード上のVRAMに確保されるため、BitBltは完全にグラフィックボード側で処理されます。そのため毎回全領域をBitBltしてもCPU負荷はほとんどかかりません。今のグラフィックボードの2D性能は、一番安いものでもそのくらい朝飯前の水準です。言い換えると2D性能は飽和しています。
      • 2Dアクセラレーションがない場合は、当然そのような無茶なことをすると遅くなります。MonaFormsが該当します。

Qt

昔の仕様は忘れてしまったので、最近いじっているQt-4の仕様を書きます。

  1. ダブルバッファがデフォルト。
    • 解除できると思いますが、そこまで使い込んでいません。
  2. 描画イベントの外では描画できない。
    • update()をかけて間接的にpaintEvent()を呼んでそこで描画します。
    • paintEvent()が呼ばれた段階でバッファはクリアされています。つまり全描画です。

これらの仕様は、現時点で出回っているハードウェアの2D性能が充分に高いという前提で、富豪的(効率よりも単純さを重視)に割り切ったものだと思われます。

AWT

BayGUIはAWTに準拠しているようなので、AWTの仕様に準拠するのが最も重要です。よく知らないので、軽く調べてみました。

http://www.asahi-net.or.jp/~DP8T-ASM/java/idiom/AWT.html

paintイベントに範囲指定が来ないようです。常に全描画するようです。

http://wisdom.sakura.ne.jp/system/java/swing/swing8.html

paintイベントごとにクリアされるようです。ただし渡されたGraphicsは既にクリッピングされているのかもしれません。それであれば、全描画してもクリッピングされた部分しか描画しないことになります。この辺はちょっと判断が付きません。

以上のことから推測をまとめます。

  1. BayGUIのpaintイベントで常に全描画というのは、AWTに忠実に準拠した実装であると言えます。
  2. GUIサーバーではダブルバッファが強制されるため、AWTの仕様と整合性を取るには、paintイベントはバッファを描画しているだけと見なすことになるでしょう。
    • つまりダブルバッファをサポートしていない環境で自前でダブルバッファするのと条件が同じです。
  3. 自前でバッファを用意していると見なせば、それをどのように扱っても構いません。
    • 描画イベントの外で描画しようと勝手です。ただしそれを反映させるため、自前でrepaint()を呼ぶ必要があります。

AWTのrepaintを調べてみました。

http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/awt/Component.html

以上から、GUIサーバーと整合性を持たせると、次のようになると思われます。

  1. paintイベントはバッファを描画するだけと見なすので、基本的にユーザーで再定義しない。
    • そうしないと整合性が取れない。
  2. 描画はバッファに対して行うため、どのようなタイミングで行っても構わない。ただしrepaint(x, y, w, h)は自前で呼ぶ、
    • 範囲をキャッシュして自動化すると便利ですが、それは本物のAWTでも、自前でそういうクラスを用意したと見なせばアリでしょう。

結局、AWTとの整合性を取るにはAWTの上に自前で便利な仕組みを用意した状況だと見なすしかないので、何でもアリです。

MonaのGUIサーバーの仕様

  1. すべてのウィンドウがダブルバッファ固定です。
  2. 描画要求はありません。
    • サーバー側でバッファから自動的に描画するようになっています。
    • 移動や順序変更による描画はサーバー側で勝手にやります。リサイズは実装されていないため考慮していません。
  3. クライアント側での描画イベントは擬似的なものです。
    • 以下はすべてMonaFormsの仕様で、BayGUIに関してはこの限りではありません。
    • システムでの描画要求はありませんが、一般的な流儀に合わせて描画イベントが起こるようにしてあります。
    • 描画イベントが発生するのは以下のケースです。
      • ウィンドウが表示される直前(これが来ないと真っ白のままになってしまう)
      • Refresh()を呼んで人為的に引き起こした場合
    • 部分描画はありません。
      • OnPaint()には引数がありません。部分描画がサポートされていないからです。またダブルバッファなので、部分描画の必要がなく、OnPaint()は丸ごと書き換えという前提で捉えているためです。
      • バッファに対する描画が一部であっても、Refresh()で範囲指定ができないため、サーバー側ではトップレベルウィンドウ単位で全描画されてしまいます。
      • この仕様は情けないくらいダメダメなので、今回はこれを直すことを主眼に説明しました。
    • 描画は描画イベントとは無関係に行えます。
      • 描画完了後に自動反映されることはないため、Refresh()を呼ばなければなりません。しかもGUIサーバーで部分描画をサポートしていなかったため、更新は必ずトップレベルウィンドウ単位になってしまいます。
      • Refresh()でOnPaint()を引き起こした場合、OnPaint()で何もしなければバッファはそのままです。Refresh()でクリアされることはありません。Refresh()以前にバッファを書き換えていれば、その内容が反映されます。
      • クリアされないのは、描画イベントと無関係に描画した場合であっても、Refresh()を呼ぶことで表示に反映させないといけないからです。
      • 描画は描画イベントに限定するのであれば、Refresh()でクリアしても構わないでしょう。それはQtの仕様に近いです。

まとめ

以上のまとめとして個別に回答します。

感想

  1. GUIサーバーがダブルバッファを強制するため、ダブルバッファが前提ではないAWTと整合性が取れない。
    • 自前のダブルバッファがあると見なして辻褄を合わせるしかない。
  2. 本来ダブルバッファは2Dアクセラレーションを前提にしてこそ真価を発揮する。
    • 2Dアクセラレーションがあれば毎回全領域をBitBltしてもびくともしない。効率よりも単純さを取った富豪的な考え方。
    • ダブルバッファしておきながら2Dアクセラレーションが期待できないため、部分描画を工夫するという羽目になっている。

ひげぽんの感想ほか。

ひげぽんからの質問2

頂いた回答に分からない部分がありましたので質問させていただきます。
お時間に余裕のあるときで結構ですので御回答頂けると助かります。

コメント

コメントはありません。 コメント/GUIサーバ高速化/04.部分描画実装(libbaygui)/描画作法?

お名前:

MENU

now: 2

リンク


最新の20件
2018-05-03 2017-09-29 2017-04-25 2017-01-10 2016-12-11 2016-10-04 2016-08-14 2016-06-05 2016-05-29 2016-04-15 2015-12-28 2013-02-25 2013-02-21 2013-02-20 2013-02-12 2013-02-11 2013-02-10
最新の20件
2010-02-01 2010-01-31 2010-01-30 2010-01-29 2010-01-16

Counter: 3996, today: 2, yesterday: 0

リロード   新規 編集 凍結 差分 添付 複製 改名   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS

Last-modified: 2008-03-28 (金) 15:47:54 (3769d);  Modified by mona
PukiWiki 1.4.6 Copyright © 2001-2005 PukiWiki Developers Team. License is GPL.
Based on "PukiWiki" 1.3 by yu-ji
Powered by PHP 5.2.17
HTML convert time to 0.075 sec.