ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

MFCでOpenGLをつかう -その1-

2022年にやることではないとは思いますが、仕事の勉強がてらMFCを使ったグラフィカルなアプリケーションを作りたくなりました. そこでOpenGLを使用したMFCアプリケーションを作ります.

なお、MFCは書籍等も中古でしか見当たらないほど過去のものであり、特別な事情がない限り使用する理由はないです(逆に言えば、今回は仕事で使いそうだという理由からMFCを利用しています).

この記事はその1として、環境構築から矩形を表示するところまでをカバーしています.

2022/8/16 ライブラリに関する記述を一部修正

2022/10/6 内容を修正

環境

項目 バージョン
OS Windows11
Visual Studio 2017 Community Edition (ツールセットv141)
Windows SDK 10.0.17763.0
Glew_static 2.1.0.3
glfw 3.38

プロジェクトの作成

まずは新しいMFCプロジェクトを作成します.ファイル->新規作成->プロジェクトからVisual C++->MFC/ATL->MFCアプリを選択し、好きなプロジェクト名を作成します. 今回はOpenGLという名前にしました. MFCアプリ作成用ウィザードが表示されるので、次の画像のようにプロジェクトを作成します.

MFCアプリケーションプロジェクトを作成

ウィザード:アプリケーションの種類

ウィザード:ドキュメント テンプレート プロパティ

ウィザード:ユーザーインターフェイス機能

ウィザード:高度な機能

ウィザード:生成されたクラス

上記の画像のようにウィザードを進めたら完了を押してプロジェクトの作成は完了です.作成されたばかりのプロジェクトをビルドすると、次のようなフレームが表示されます.

初ビルドの結果

とりあえずコントロールOpenGLで描画

さて、ここから実際にMFCのコントロールOpenGLを用いて描画していきます. 次の記事を参考にさせていただきました.

sourcechord.hatenablog.com

ざっくりとした流れは次になります

  • ピクチャーコントロールをダイアログに追加する
  • CStaticクラスを継承したCGLScreenクラスを作る
  • ダイアログのソースにCGLScreen型のDDX変数を追加
  • CGLScreenのデバイスコンテキストを取得し、OpenGLで矩形を描画する

ピクチャーコントロールの追加

リソースビュー ->OpenGL->OpenGL.rc ->Dialog->IDD_OPENGL_DIALOGを選択し、リソースエディタを開きます. リソースエディタを表示中にツールボックスをクリックすると、様々なコントロールが表示されています. 検索窓でPicture Controlと検索し、Picture Controlをドラッグ&ドロップしてダイアログに追加しましょう. 邪魔なので、中心にあるラベルは削除しておきましょう.

IDD_OPENGL_DIALOG

Picture Control をドラッグアンドドロップしてダイアログに追加

Picture Control追加直後は、コントロールのIDがID_STATICになっているかと思います. 今回はIDC_GLという名前に変更します.

Picture Control のIDをIDC_GLに

CGLScreenクラスの定義

次にPicture Control用の独自クラスを実装するため、ソリューションエクスプローラー->右クリック->追加->クラス からクラスのソースファイルとヘッダーファイルを同時に生成します.

CGLScreenクラスのソースとヘッダーの生成

CGLScreen.h

生成されたCGLScreen.hを以下のようにします.

// CGLScreen.h
#pragma once
#include <afxwin.h>
class CGLScreen : public CStatic
{
    protected:
    HGLRC m_hRC;
    CDC* m_pDC;
    BOOL SetupPixelFormat();
    BOOL InitGLContext();

public:
        virtual void PreSubclassWindow();
    DECLARE_MESSAGE_MAP()
    virtual afx_msg void OnPaint();
    virtual afx_msg void OnDestroy();
};

CGLScreen.cpp

生成されたCGLScreen.cppに下のようなコードを追記します.

// CGLScreen.cpp
#include "CGLScreen.h"
#include <GL/GL.h>
#include <GL/GLU.h>

BOOL CGLScreen::SetupPixelFormat()
{
    PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA,
        24,
        0, 0, 0, 0, 0, 0,
        0,
        0,
        0,
        0,  0, 0, 0,
        16,
        0,
        0,
        PFD_MAIN_PLANE,
        0,
        0, 0, 0
    };

    int pixelformat;
    if (0 == (pixelformat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd))) {
        return FALSE;
    }

    if (FALSE == ::SetPixelFormat(m_pDC->GetSafeHdc(), pixelformat, &pfd)) {
        return FALSE;
    }

    return TRUE;
}

BOOL CGLScreen::InitGLContext()
{
    m_pDC = new CClientDC(this);

    if (NULL == m_pDC) {
        return FALSE;
    }
    if (!SetupPixelFormat()) return FALSE;
    if (0 == (m_hRC = ::wglCreateContext(m_pDC->GetSafeHdc()))) {
        return FALSE;
    }
    if (FALSE == ::wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC)) {
        return FALSE;
    }

    return TRUE;
}

void CGLScreen::PreSubclassWindow()
{
    LONG  style = GetWindowLong(this->m_hWnd, GWL_STYLE);
    style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
    SetWindowLong(this->m_hWnd, GWL_STYLE, style);


    InitGLContext();
    CStatic::PreSubclassWindow();

}

BEGIN_MESSAGE_MAP(CGLScreen, CStatic)
    ON_WM_PAINT()
    ON_WM_DESTROY()
END_MESSAGE_MAP()


void CGLScreen::OnPaint(){
    CPaintDC dc(this); // device context for painting

    ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //とりあえず、四角形を描画してみる。
    ::glPushMatrix();
    ::glColor3f(1.0f, 1.0f, 0.0f);
    
    glColor3d(1.0, 0.0, 0.0);
    glBegin(GL_POLYGON);
    glVertex2d(-0.9, -0.9);
    glVertex2d(0.9, -0.9);
    glVertex2d(0.9, 0.9);
    glVertex2d(-0.9, 0.9);
    glEnd();

    ::glPopMatrix();
    ::glFinish();

    if (FALSE == ::SwapBuffers(m_pDC->GetSafeHdc())) {}
}

void CGLScreen::OnDestroy()
{
    CStatic::OnDestroy();

    // TODO: ここにメッセージ ハンドラ コードを追加します。
    if (FALSE == ::wglMakeCurrent(NULL, NULL)) {
        // 必要に応じてエラーハンドリング
    }

    if (FALSE == ::wglDeleteContext(m_hRC)) {
        // 必要に応じてエラーハンドリング
    }

    if (m_pDC) delete m_pDC;
}

ここでOnPaint()とOnDestroy()の解説をば. MFCアプリケーションはメッセージによるイベント駆動を基本に動作します. つまり、イベントハンドラを書き足してアプリケーションを作るのですが、その際にどのメッセージを有効にするかということを明示しなければなりません. メッセージを有効にする場合、BEGIN_MESSAGE_MAP()~END_MESSAGE_MAP()で囲まれた部分に有効にしたいメッセージとそのメッセージに対するハンドラを登録します. JavaScriptでいうところのaddEventListenerみたいなものですね. 今回の場合はWM_PAINTとWM_DESTROYというメッセージに対するイベントハンドラを登録していて、これらのメッセージを受信したときにハンドラが自動的に実行されるようにしています.

PreSubclassWindow()は自動的に呼び出されるという点ではイベントハンドラに似ていますが、フレームワークによって呼び出される関数です. なので、対応するメッセージなどは存在せず、メッセージマップに登録する必要はありません.

DDX変数のダイアログへの追加

Picture Controlと先ほど定義したクラスを紐づけるため、DDX変数を追加します. より正確に言えば、IDC_GLとバインドさせたCGLScreen型の変数m_GLScreenを、OpenGLDlgクラスに追加します.

Picture Control を追加したときのようにリソースエディタを開き、DDX変数を追加したいコントロールを右クリック->変数の追加からウィザードを開きます.

リソースエディタからPicture Control のDDXを追加する

コントロール変数の追加ウィザードのコントロールタブを次のようにします. なお、その他タブはデフォルトのままで大丈夫です.

ウィザード:コントロール変数の追加

ウィザードを完了した後にOpenGLDlg.hを見ると、CGLScreen型の変数m_GLScreenが追加されていることが分かります. ただし、CGLScreen.hをインクルードしていないので、赤い破線が表示されています.

DDX変数追加直後のOpenGLDlg.h

なので、OpenGLDlg.h内でCGLScreen.hをインクルードしましょう.

OpenGLDlg.h

// OpenGLDlg.h
// m_GLScreenが追加された

#pragma once
#include "CGLScreen.h"

class COpenGLDlg : public CDialogEx
{
public:
    COpenGLDlg(CWnd* pParent = nullptr);

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_OPENGL_DIALOG };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);  // DDX/DDV サポート


// 実装
protected:
    HICON m_hIcon;

    // 生成された、メッセージ割り当て関数
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    CGLScreen m_GLScreen;

};

以上でファイルの編集は完了です.

いざビルド

ビルド結果
以上のように無事MFCのコントロールOpenGLを使って描画することが出来ました.

まとめ

Visual Studio 2017でもMFCOpenGLを連携して使用することが出来ました. VC++でイベント駆動アプリケーションを作るのは抵抗がありましたが、やってみるとほかの言語とそこまで変わらなくて驚きました.

次回は頂点バッファオブジェクトと頂点配列オブジェクトを使った矩形の描画をしていきます.

参考

stackoverflow.com sourcechord.hatenablog.com

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com