WPFでOpenGLプログラミング


(本記事は書きかけです)
やあ子供たち。元気にしてたかい。久々の更新となる今回はWindowsプラットフォームにおける後発のユーザーインタフェース開発環境であるWPF環境において、OpenGL窓を実装するお話だよ。今回はOpenTKやTaoFramework、SharpGLといった外部フレームワークに一切頼らないアプローチを紹介するよ。
背景的なこと
ご周知のとおり、WPFとは、ボタンに代表される各種ユーザーインタフェース部品や、テキストやグラフィックの描画を、全て単一のウィンドウの中に、なんとDirect2Dを使って描画しまくることで、自由なユーザーインタフェース構築を可能とするしくみですよね。(ここここ参照。)
しかし、旧来のHWNDを持ったウィンドウを対象として機能する資産というか資源というかプログラムまたは部品がたくさんあるので、やはり旧来のHWNDベースのウィンドウもその中に表示・機能できるようになってないとまずいね、というわけで、WindowsFormsHostやHwndHostといったクラスというか手段が用意されています。(今回は後者を用いる場合のメモとなります。)
このHostクラスというのは、Direct2Dで描画する本来のインタフェース配置エリアにすっぽり穴を開けて領域を確保し、その領域の中にHWNDベースのウィンドウを描画しましょうというしくみ。具体的には、このHostクラスというものが、それ自体、HWNDのラッパーであり、つまりこのHost君がHWNDなウィンドウだからということで、その子ウィンドウとして、さまざまないわゆるWin32セカイのHWND持ちのウィンドウたちを実現させてくれるという仕組みだ。

ちなみに、本記事の方法を確立する上では、ここや、ここの内容を大いに参考にしています。また、本記事では無償入手が可能なVisualStudio2013ExpressEditionを使用しています。
さてそれでは本題に入って行きましょう。
本記事では、冒頭の図ような、WPFインタフェースの中に、OpenGLウィンドウがはまり込んでいるテストアプリを作成します。
超ざっくりとした説明
元来OpenGLC/C++でしか使えないため、OpenGLを描画するHWNDベースのWindowは、C++/CLIコードの中で定義します。
以下に紹介するHwndHostを活用した、OpenGL描画領域を備えたWPFC#アプリは、まずMainWindowの中に設置されたBorder部品があり、その子ウィンドウとして、HwndHost派生クラスのインスタンスがあり、同インスタンスのCoreウィンドウの子ウィンドウとして、C++/CLIで定義した、OpenGL描画用のウィンドウがある、という構造になります。

ややざっくりとした説明

  1. C#WPFアプリを作成し、OpenGLの描画領域としたい場所に「Border」ウィジェットを設置する。
  2. 以下の最低限のメソッドをオーバーライドした、HwndHost派生クラスを定義しておく。
    • BuildWindowCore()
    • WndProc()
    • DestroyWindowCore()
  3. HwndHost派生クラスのインスタンスを作成し、1.で作成したBorder部品の子ウィンドウとする。
  4. 予めC++/CLIのDLLとして定義しておいたOpenGLViewクラスのインスタンスを、3.で作成したHwndHost派生ウィンドウの子ウィンドウとして作成。
  5. あとはサイズ調整などのチップスの実装を少々。

C++/CLIで用意する、OpenGLViewの定義の仕方に関するもうちょっと詳細な説明
すでにお気づきのように、C#コードとC++/CLIコードとは共存できないので、
C++/CLI側の実装はDLLの形にして、これをWPFアプリ側からリンクして利用するという形をとります。実際には、「新規プロジェクト追加」>「空のCLRプロジェクト」を作成し、ターゲットをDLLとします。
OpenGLを扱う際には、ターゲットのHWNDとセットで、レンダリングコンテキスト(HGLRC型)というもののインスタンスを保持しなくてはなりません。やり方は様々でしょうが、ここではこの二つのインスタンスのハンドルをまとめて保持しておくための目的で、OpenGLViewクラスを用意します。WPFアプリ側からも参照したいので、マネージドクラスとして実装します。

 【OpenGLViewクラスの定義】
 ・C++/CLIで空のプロジェクトを作成する。
  (必要ならプロジェクトのプロパティでターゲットアーキテクチャをx64にする)
 ・プロジェクトのプロパティより、ターゲットをDLLにする。
 ・OpenGLViewクラスを定義する。
  ・コンストラクタの中で、
   HWND型のメンバ変数をターゲットにビューとなるウィンドウを作成する。
   ・RegisterClass(), CreateWindow() Win32APIなど使用。
    ウィンドウプロシージャも定義。
   ・コンストラクタでは入力引数として、
    作成すべきビューの縦横サイズを受け取れるようにしておくと
    よいだろう。
  ・HWNDメンバーに外部(この場合WPFアプリ)
   からアクセスするためのgetメソッドを実装。