OpenGLを使ってみる。その4 -描画スレッド-
リアルタイムな描画を行うために、描画スレッドを導入します。
一般的にはメインスレッド内でPeekMessageを使ってwindowsイベントを処理し、
アイドル時に描画する形になると思いますが、
メインスレッドはwindowsイベントに専念し、サブスレッドで描画してみます。
サンプル
- opengl04.zip
- VC++2008EEで作成
- opengl.exeを実行してください
描画スレッド
boost::threadのようにテンプレートで実装したいところですが、
無駄に規模が大きくなりそうなので、もっと単純なスレッドクラスを作ってみます。
thread_baseクラス
- 最小限のスレッド基底クラス
- 派生クラスでrunを実装する
class thread_base { private: HANDLE handle; HANDLE end_event; HANDLE stop_event; public: thread_base() : handle( 0 ) , end_event( 0 ) , stop_event( 0 ) { end_event = CreateEvent( 0, 0, 0, 0 ); stop_event = CreateEvent( 0, TRUE, TRUE, 0 ); } virtual ~thread_base() { if( end_event ) { CloseHandle( end_event ); end_event = 0; } if( stop_event ) { CloseHandle( stop_event ); stop_event = 0; } if( handle ) { CloseHandle( handle ); handle = 0; } } public: void start() { if( !handle ) handle = ( HANDLE )_beginthreadex( 0, 0, &thread_base::entry_point, this, 0, 0 ); else SetEvent( stop_event ); } void stop() { if( handle ) ResetEvent( stop_event ); } void join() { WaitForSingleObject( handle, INFINITE ); } bool is_active() { //ループ停止用 WaitForSingleObject( stop_event, INFINITE ); return WaitForSingleObject( end_event, 0 ) != WAIT_OBJECT_0; } void end() { SetEvent( end_event ); } private: static unsigned __stdcall entry_point( void* param ) { thread_base* t = reinterpret_cast< thread_base* >( param ); if( t )t->run(); return 0; } virtual void run() = 0; };
render_threadクラス
- レンダリングループ
- 終了させるには、外部からendメソッドを呼び出す
class render_thread : public thread_base { public: void run() { while( is_active() ) { レンダリングコンテキストをセット; 描画; SwapBuffers( wglGetCurrentDC() ); レンダリングコンテキストを解除; Sleep( 1 ); } } };
WinMain
- ウィンドウ生成後にレンダリングスレッドを生成
render_thread t; int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { ウィンドウ生成; OpenGL初期化; //レンダリングスレッド開始 t.start(); // メイン メッセージ ループ: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } //レンダリングスレッド終了 t.end(); t.join(); OpenGL破棄 return (int) msg.wParam; }
ウィンドウのサイズ変更時
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { /*...*/ case WM_SIZE: { レンダリングコンテキストを設定; ビューポートの設定( LOWORD( lParam ), HIWORD( lParam ) ); 描画; SwapBuffers( wglGetCurrentDC() ); レンダリングコンテキスト解除; } break; case WM_ENTERSIZEMOVE: t.stop(); break; case WM_EXITSIZEMOVE: t.start(); break; /*...*/ }
マルチスレッドは難しいですね。
レンダリングコンテキストの切り替えと、ウィンドウのサイズ変更時の処理で、かなり苦労しました。
とりあえず動いていますが、スレッド周りでバグがあるかもしれません。
次回からは3Dの描画を行いつつ、MVCを意識した形に修正していこうと思います。