libbayguiに部分描画を実装する †
- ブランチを作ろう
- Component/Image/Buttonのソースを見よう
- Component
- repaint中で paint(g); update()が呼ばれる
- c->getGraphics()->drawImage(this->_buffer, getX(), getY()); (cは親または自分自身)
- updateでは部品全体が
- Button
- 描画フロー
- repaint();
- paint(g);
- update();
- update(x, y, w, h); // コンポーネント自身の大きさ
- c=親または自分自身
- c->getGraphics()->drawImage(this->_buffer, getX(), getY());
- c->update(c->getX() + c->getInsets()->left + x, c->getY() + c->getInsets()->top + y, w, h);
- c->getGraphics()->drawImage(this->_buffer, getX(), getY());が肝心な部分。
- drawImageは、引数で受け取ったimageを Graphicsのimage(x,y)に転送する
- つまりコンポーネントのimageを親にコピーする
- この呼び出し連鎖はWindowのupdateまで到達しそこで初めて MSG_GUISERVER_DRAWWINDOW が発行される
- アプリケーションに対してどう見せるか?
- repaint(x, y, w, h)を用意する。これはプログラマが明確に再描画範囲を指定する。
- 確実だけど。プログラマはしていするのが面倒。dryで行こうよ。
- 今までと同じく repaint() を呼べば変更部分のみ実際の描画を行ってくれる。
- 実現方法は例えば、前回の描画イメージと差分をとるとか。
- でもあまり効率の良いものではないのでどうしようか。と考えていたんですがどの座標に描画したかを知っている人がいる。
- →それは Graphics君だ。
- Graphicsメモ
- Window::__g がトップレベルウィンドウバッファ
- Window::_g がウィンドウバッファ
- とりあえず _g は無視。
- ここまで来てTinoさんが「Graphics側でupdate()がかかるまでの描画部分を覚えておくと、BayGUIの修正だけでアプリ側の修正が必要ないでしょう。」の意味を理解する。
- 以下のような感じで進めてみよう。
- なければRectangleクラスを作る。
- Graphicsにまだ描画されていない Rectangle のリストを保持する
- HList<Rectangle*> rectangles;に保持するようにした。
- DrawXXXで領域を記録
- update時にはその時点の Rectangleを部分描画し、Rectangleリストをクリアする
- updateの直前に全てのRectangleが収まるような Rectangle を計算する。→再描画ということで良いかな。
- 部分描画領域 Rectangle を update へつなぐところが不明確なので要調査。
- 気になる点は?
- 今日の作業(お昼の部 2時間)は終了。夜に体の調子が良ければ続きをやります。
- 熱で朦朧としつつ書いた。
- このあたり の Graphics.cpp, Window.cppかな。
- 全然速くなっていないので何か間違っているっぽい。明日デバッグします。
- drawPixelが多すぎると速度的に不利なのかも。
- いや部品単位でやらないとダメな気もする
- Rectangleリストの導入で描画範囲は節約できたのか?
- →bayguiの起動までのログをとってみよう
- Rectangleリスト導入前
DrawWindow:(720, 22, 64, 64)
DrawWindow:(720, 22, 64, 64)
DrawWindow:(720, 22, 64, 64)
DrawWindow:(720, 86, 64, 64)
DrawWindow:(720, 86, 64, 64)
DrawWindow:(720, 510, 64, 64)
DrawWindow:(720, 86, 64, 64)
DrawWindow:(720, 510, 64, 64)
- Rectangleリスト導入後
DrawWindow:(0, 0, 10, 10)
DrawWindow:(0, 0, 10, 10)
DrawWindow:(0, 0, 10, 10)
DrawWindow:(0, 0, 6, 6)
DrawWindow:(0, 0, 6, 6)
DrawWindow:(0, 0, 6, 6)
DrawWindow:(0, 0, 17, 17)
DrawWindow:(0, 0, 17, 17)
- かなりの改善が行われているが見た目上あまり変わっていないように見える
- DrawWindowの処理側がちゃんと部分描画していない?
- Rectangleの領域計算に時間がかかっている?
- 調べてみた所怪しい点は2つ
- 部分描画のx, yが常に 0 なのがおかしい気がする
- GUIサーバーで、arg2/arg3が0の条件を見直した方が良さそう。
- 部分描画のx, yが0問題は原因が分かった
- 誤:MonAPI::Message::sendReceive(NULL, this->guisvrID, MSG_GUISERVER_DRAWWINDOW, getHandle(), MAKE_DWORD(r->x , r->y), MAKE_DWORD(r->width, r->height));
- 正:MonAPI::Message::sendReceive(NULL, this->guisvrID, MSG_GUISERVER_DRAWWINDOW, getHandle(), MAKE_DWORD(r->x + x , r->y + y), MAKE_DWORD(r->width, r->height));
- うまくいってないで悩んでいる部分
- MSG_GUISERVER_DRAWWINDOWの手前で以下のように描画範囲をログ出力しているが、gnote/シェルともにトップレベルウィンドウバッファ全体が再描画されているように見える。どこかでGraphicsでimage全体への描画が入ってしまっているのだと思うけど。
- 怪しいのは Component::update(int x, int y, int w, int h) の中の c->getGraphics()->drawImage(this->_buffer, getX(), getY());なんだけどうーん違うかなぁ
- logprintf("DrawWindow:(%d, %d, %d, %d)\n", r->x + x, r->y + y, r->width, r->height);
- GUIシェルの領域がどのような座標系・順序で描画まで結び付くか?を今までよりも厳密に調べる。
- gshellのキーイベントを見ると 1文字追加する際に
- paint(getGraphics())を呼んでいる
- paintでは引数で受け取った g で drawStringなどを1からやりなおしている。
- そしてpaintの最後で update()を呼び出している。
- これだと部分描画の恩恵を一切受けられないことになってしまう。
- libbayguiの変更だけで、アプリケーションの変更がないという前提が間違っている気がしてきた。
- ちがうかな・・・
コメント †
コメントはありません。 コメント/GUIサーバ高速化/04.部分描画実装(libbaygui)?
- これは非常に重いテーマなのでページを分割します。⇒./描画作法 -- Tino
- 今後のことを考えると・・・の部分は了解致しました。その通りだと思います。純粋な質問なのですが、良く見るGUIフレームワークでは OnPaint(g)のような所には全体再描画のためのコードを書き、ウィンドウ移動や状態変更時にフレームワークからcallされると理解しています。小さなアプリであれば描画にかかわることは全てOnPaintに書くだけで済ませ、イベント取得時に描画内容を変えたい場合は描画に使われる変数などを変更後、Repaint()をcallすることでPaint()を呼んでもらう形が一般的だと理解しています。(BayGUIもこのような思想に則ったものであると理解しています。)。ただ今回のような場合、キーイベント時に直接Graphicsオブジェクトに対して描画を行い、その後Repaint()またはPaint()を呼ぶことがパフォーマンスアップがつながるようですが、このような手法は一般的なのでしょうか。知りたいのはアプリケーション開発者はどのようなコードを書くことを一般的なGUIフレームワークで期待されているかという部分です。より具体的に言えば、BayGUIがBayGUIアプリ開発者に書くことを期待しているコードは一般的なGUIフレームワークと合致しているか。GShellは一般的なGUIアプリのコードの書き方に合致しているか。の2点です。 -- ひげぽん
- repaint(x, y, w, h)を実装しても構わないと思いますが、アプリケーション側で更新部分を特定してrepaint(x, y, w, h)を呼ぶのと、アプリケーション側で更新部分だけを描画して再描画は任せるようにするのとでは、どちらもかなり深い所までの見直しが必要で工数が劇的に違うようには思えません。既存のものではなく、今後新しく作るものの便宜を考えると、自分で範囲指定するより任せた方が仕様としては楽なのかなとは思います。 -- Tino
- 謝っていただく必要は一切ありません。むしろいろいろ教えていただいて感謝しています。gshellよりもgnoteを速くしたいというのが今回の目的なので別方向で考えてみようと思います。アプリケーションが呼ぶことのできる repaint(x, y, w, h)を用意するのがよい気がするのですがGUIの設計的にありでしょうか。 -- ひげぽん
- なるほど、gshellがそのような仕様なら部分描画の恩恵はありませんね。私の見込み違いでした。申し訳ありません。MonaFormsのGUIシェルは追加した文字だけを描画していたはずです。この辺の設計の違いは、元のBayGUIがダブルバッファではない環境だったことに起因するのかもしれません。ダブルバッファではない環境では以前描画した文字が保持されている保障がないので、全部再描画する実装になるのは必然です。MonaFormsは最初からダブルバッファを前提にした作りになっています。この辺のバランスを考えて、メモリを浪費してでもダブルバッファをデフォルトにしたという経緯があります。 -- Tino
- 行き詰ったらSDL版BayGUIでgnoteを動かしてみると良いかもしれません。エミュレータを挟まないので、デバッグ効率が桁違いに向上するかもしれません。私の経験ですが、以前MonaFormsのバグが取りきれないときに、Win32版を作ってデバッグしたことがあります。ただ私はBayGUIのことはよく分からないので、SDL版の導入手順などはbaysideさんに相談する必要がありそうですが。 -- Tino
- 相談の手間をかける前に、もう少し自力でがんばってみようと思います。 --ひげぽん
- 参考までに、以前libguiの実装が間違っていて、孫ウィンドウにイベントを渡すときローカル座標系に直せていないことがありました。一階層上しか見ていないと親ウィンドウのローカル座標系になってしまうので注意が必要です。MonaDatのときのlibguiのパッチでその辺を修正したものがあったような記憶があります。 -- Tino
- ありがとうございます。細かく座標系を見ていこうと思います。 --ひげぽん
- 座標系を見直すかどうかはともかく、一点気になったのは、現状の仕様はグローバル座標系なので、MAKE_DWORD(r->x + x , r->y + y)はちゃんとグローバル座標系になっているでしょうか? -- Tino
- r->x だとグローバルにならず + x するとグローバルになっているようです。(そう見えます。)。もっと細かく見ていこうと思います。 --ひげぽん
- あまりごちゃごちゃ言って見落とされるとまずいので指摘しませんでしたが(それでも見落とされましたが・・・)、arg2がグローバル座標系なのは気になっていました。引数にウィンドウが含まれているので、ローカル座標系の方が自然のような気がします。 -- Tino
- ここはまだきちんと分かっていないので保留とさせてください。--ひげぽん
- 「GUIサーバーで、arg2/arg3が0の条件を見直した方が良さそう。」ですが、この点は既に03で指摘しました。↓ -- Tino
- 部分描画のコードについて一点だけ。条件判断のmsg->arg2 != 0は抜いた方が良いです。画面の左上から描画する際に誤判定されてしまいます。arg3がサイズなので、サイズが0でなければ部分描画と判定しても問題ないでしょう。 -- Tino
- あぁ。失礼しました。調査して過去のTinoさんの指摘を読みかえしてを繰り返していたのですが見落としていました申し訳ありません。 -- ひげぽん