Qt 4.8 での OpenGL のスレッド利用

この記事は Qt Blog の "Threaded OpenGL in 4.8" を翻訳したものです。
執筆: Jason Barron 2011年6月3日

Qt の OpenGL モジュールを使用したことのある方ならば、OpenGL を別スレッドで動かしたいと考えたことがあるかもしれません。OpenGL の実装自体は(ほとんどが)リエントラントなので、それに対する障害はありません。実際、古めの記事ですが Qt Quarterly の [qt "第6回" l=qq06-glimpsing m=#writingmultithreadedglapplications v=qq] でその方法を説明しました。普段から OpenGL のコードを直接書いているような人々にとっては役に立つ記事ですが、Qt の有用なクラス群も別スレッドで使いたい場合にはどうでしょう。残念ながらそれらの Qt のクラスがスレッドセーフではなかったため、それは不可能でした。Qt 4.8 ではその修正を行い、多くの共通するシナリオのいくつかをサポートしました。X11 でこの新機能を使うには [qt "Qt::AA_X11InitThreads" l=qt m=#ApplicationAttribute-enum v=master-snapshot] アプリケーション属性を有効にして、GLX の呼び出しがスレッドセーフになるようにする必要があります。Windows と Mac OS X ではこの操作を行わなくても動くはずです。

バッファ交換スレッド

GPU やそのドライバにもよりますが、swapBuffers() の呼び出しコストは(特に組み込みプロセッサでは)高くつくことがあります。多くの場合、この関数は GPU に全ての描画コマンドを現在のフレームに対して実行するように命じます。これが同期動作する場合、GPU が動いている間メインスレッドがブロックされます。スレッドには他にやるべき事がたくさんあるので、GPU の動作が終了するのをただ待っているのは無意味です。例えばその時間で、イベントループに戻ってネットワークやユーザからの入力を処理したり、次のアニメーションを準備したりできます。4.8 ではその解決として、swapBuffers を呼び出して GPU の処理が終わるのを待つためだけの別スレッドを作成します。実際には、メインスレッドで今まで通り全てを描画して、最後に swapBuffers() を呼び出す代わりに、現在の GL コンテキストで doneCurrent() を呼び出します。それを受けてバッファ交換スレッドでは描画が終了したことを検知し、makeCurrent() でコンテキストを作成、アクティブにして swapBufffers() を呼び出します。バッファ交換スレッドでの処理が終了すると doneCurrent() を呼び出してメインスレッドに通知します。これらの処理ではコンテキストの変更が生じるため、そのオーバーヘッドで実際にはメインスレッドがただ GPU の処理が終わるのを待つよりも処理が遅くなる可能性があることに注意してください。そのため、実際に処理が速くなるのかを確認することが重要です。

テクスチャアップロードスレッド

通常、多量の(あるいは巨大な)テクスチャのアップロードは、GPU に大量のデータを転送する典型的なコストの高い処理です。また、これは再びメインスレッドを無意味にブロックする可能性のある処理の一つでもあります。4.8 では共有された QGLWidget のペアを作ることによってこの問題を解決できます。そのウィジェットの一つは別スレッドで作成されますが、決して画面に表示されることはありません。メインスレッドはアップロードスレッドにどの画像をアップロードするかを通知し、アップロードスレッドではそれぞれの画像に bindTexture() を呼んでそれぞれをアップロードします。画像のアップロードが終了するたびにメインスレッドに通知し、画面への描画を開始できます。

QPainter スレッド

4.8 では OpenGL [ES] 2.0 ペイントエンジンを使用している場合、QPainter を使用した QGLWidget や QGLPixelBuffer, QGLFrameBufferObject への描画を、別スレッドで行うことができます。QGLWidget が(結局は QWidget の派生クラスなので)別スレッドに属するわけではないことに注意するのは重要です。しかし、そのコンテキストは他の場所から使用できます。そのためには QGLWidget で doneCurrent() を呼び出して、メインスレッドからコンテキストを解放する必要があります。次に、描画用のサブスレッドで動かすための QObject の派生クラスを作ります。この QObject は描画用サブスレッドで QGLWidget のコンテキストを作成し、QPainter を使って描画を開始します。描画対象が QGLWidget の場合、それに加えて “Qt::WA_PaintOutsidePaintEvent” 属性を QGLWidget にセットする必要があります。また、QGLWidget の派生クラスを作成して resizeEvent() と paintEvent() 関数をオーバーライドする必要もあります。これらの関数のデフォルト実装では、メインスレッド(ウィジェットの属するスレッド)から関数が呼ばれるため、QGLWidget のコンテキストを作成しようとします。コンテキストは描画用サブスレッドで使用するため、それが起こっては困ります。オーバーライドした関数では、描画を行っている QObject の派生クラスにビューポートのリサイズやシーンの再描画を必要に応じて通知してください。上記のデモとして、MDI の QGLWidget を含むサブウィンドウを別スレッドで描画する glhypnotizer のスクリーンショットを以下に掲載します。

Qt 4.8のdemos/glhypnotizerのスクリーンショット


Blog Topics:

Comments