GTK+自作ウィジェットの描画処理を軽くする

inagaki inagaki

グラフィックアクセラレータのない組み込み環境でGTK+を使う場合、描画処理はけっこう負荷の高い処理です。例えば画像を大量に描画すると、その負荷が高くてバックグラウンドで別の処理を進めることができないということも起こりえます。
そこでGtkDrawingやGtkLayoutに自作の描画ハンドラ (exposeイベントのシグナルハンドラ――以下、exposeハンドラ) を作るときに無駄な描画処理をしないためのテクニックを紹介したいと思います。

背景色を正しく設定する

ウィジェットのある部分についてexposeイベント (ひらたく言えば描画イベント) が発生すると、当該部分が背景色で塗り潰されてからexposeハンドラが呼ばれます。ここを別な色で塗り直すのは当然無駄なわけで、ウィジェットの生涯を通じて最も描画面積の大きい色を背景色として設定しておけば、描画処理は軽くなるでしょう。

クリッピングする

ウィジェットの重なりが変更されたり、GtkLabelなどの自分のGdkWindowを持たないウィジェットの内容が変更されたりすると、
下になっているウィジェットの重なっている部分が再描画されます。
exposeハンドラでは、GdkGCを設定して、ウィジェットの必要な部分だけを書き換えるようにしましょう。

GdkRegionを使う方法

この方法が推奨されているようです:

static gboolean
my_widget_expose_event_handler (GtkWidget *widget, GdkEventExpose *event) {
  GdkGC *gc = widget->style->fg_gc[GTK_WIDGET_STATE(widget)];
  gdk_gc_set_clip_region(gc, event->region);
  /* 描画処理 */
  gdk_gc_set_clip_region(gc, NULL);
  /* 他の処理 */
}

GdkRectangleを使う方法

static gboolean
my_widget_expose_event_handler (GtkWidget *widget, GdkEventExpose *event) {
  GdkGC *gc = widget->style->fg_gc[GTK_WIDGET_STATE(widget)];
   gdk_gc_set_clip_rectangle(gc, &event->area);
  /* 描画処理 */
  gdk_gc_set_clip_rectangle(gc, NULL);
  /* 他の処理 */
}

ウィジェット全体を再描画するのはやめよう

クリッピングとも関係のある話ですが、手抜きをしてgtk_widget_queue_redraw_areaを使うのは避けるべきです。gtk_widget_queue_redraw_area を多用している場合は設計を見直した方がいいかも知れません。
特に画像を重ねて描画している場合、効率よく再描画するために重なりを管理する処理を書くことになりがちです。それはGTK+が既に持っている機能なので自分で作るべきではありません。

バグのあるウィジェットは使わない

バグのあるウィジェットとはGtkFixedのことですが、子ウィジェットを動かすとGtkFixed全体が再描画されるというバグがあります。

具体的なことはgtk_fixed_moveとgtk_layout_moveの実装を比較すると分かりますし、直すことも難しくありません。しかしGtkFixedは設計も古く (よく言えば単純で)、あんまりメンテナンスされていないようなので使わない方がいいでしょう。

おしまい

ぶっちゃけGTK+は独学なのであまり偉そうなことは言えません。むしろ教えてください。
Happy hacking!