OpenGLを使ってみる。その4 -描画スレッド-


リアルタイムな描画を行うために、描画スレッドを導入します。
一般的にはメインスレッド内でPeekMessageを使ってwindowsイベントを処理し、
アイドル時に描画する形になると思いますが、
メインスレッドはwindowsイベントに専念し、サブスレッドで描画してみます。

サンプル

描画スレッド

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を意識した形に修正していこうと思います。