2014年3月29日土曜日

遠回りで学ぶWin32 APIでOpenGL 004

遠回りで学ぶWin32 APIでOpenGL 003の続き

デバイスコンテキストの取得



ようやくOpenGLの環境設定に入る.まず,生成したウィンドウに対してデバイスコンテキストを取得する必要がある.これは,ウィンドウハンドル(HWND)に対して,GetDC関数(MSDNのドキュメント)を使うことで取得できる.GetDCで取得したデバイスコンテキストのハンドルは,ReleaseDC関数(MSDNのドキュメント)で解放する必要がある.

// 010_get_device_context.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

bool onCreate(HWND hWnd);
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    // ウィンドウクラスの登録
    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = WindowProc;

    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウサイズの計算
    LONG const WIDTH = 640;
    LONG const HEIGHT = 480;
    RECT r = { 0, 0, WIDTH, HEIGHT };
    DWORD windowStyle = WS_OVERLAPPEDWINDOW;
    if(!AdjustWindowRect(&r, windowStyle, FALSE)){
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウ生成
    HWND hWnd = CreateWindow(
        wcx.lpszClassName,
        TEXT("hello"),
        windowStyle,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        r.right - r.left,
        r.bottom - r.top,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウ表示
    ShowWindow(hWnd, nCmdShow);

    // メッセージループ
    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            DispatchMessage(&msg);
        }else{
            Sleep(100);
        }
    }

    cout << "終了します.何か入力してください." << endl;
    cin.get();

    return 0;
}

/*!
 *  ウィンドウに送られたメッセージを処理するためのコールバック関数
 */
LRESULT CALLBACK WindowProc(
    HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    switch(msg){
    case WM_CREATE:
        if(!onCreate(hWnd)){
            return -1;
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

/*!
 *  ウィンドウ生成時の処理
 */
bool onCreate(HWND hWnd)
{
    // デバイスコンテキストの取得
    HDC hDC = GetDC(hWnd);
    if(hDC == NULL){
        return false;
    }

    // ここに色々な初期化処理を書く

    // デバイスコンテキストの解放
    if(!ReleaseDC(hWnd, hDC)){
        return false;
    }

    return true;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    LPSTR pBuffer = NULL;
    DWORD error = GetLastError();
    DWORD result = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast(&pBuffer),
        0,
        NULL
    );

    if(result > 0 && pBuffer){
        cout << pBuffer << endl;
        LocalFree(pBuffer);
    }
}

ウィンドウプロシージャで処理するメッセージにWM_CREATE(MSDNのドキュメント)を追加している.MSDNのドキュメントによれば,-1を返せばウィンドウが生成されない,とのことなので,OpenGLの初期化に失敗した(つまりonCreateがfalseを返した)場合に-1を返すようにしてある.

コンパイル


cl /EHsc 010_get_device_context.cpp using_console.cpp /link User32.lib

デバイスコンテキストのクラス化



コンストラクタでデバイスコンテキストを取得し,デストラクタで解放するようなクラスを作っておく.
これで解放し忘れることが減る.

// device_context_handle.h
#ifndef DEVICE_CONTEXT_HANDLE_H_INCLUDED
#define DEVICE_CONTEXT_HANDLE_H_INCLUDED

#include <tchar.h>
#include <Windows.h>

class device_context_handle {
public:
    device_context_handle(HWND hWnd);
    ~device_context_handle();

    operator HDC() const;

private:
    // コピー禁止
    device_context_handle(device_context_handle const &);
    device_context_handle & operator=(device_context_handle const &);

    HWND mhWnd;
    HDC  mhDC;
};

#endif // DEVICE_CONTEXT_HANDLE_H_INCLUDED
// device_context_handle.cpp
#include "device_context_handle.h"

device_context_handle::device_context_handle(HWND hWnd)
    :   mhWnd(hWnd),
        mhDC(GetDC(hWnd))
{}

device_context_handle::~device_context_handle()
{
    if(mhDC){
        ReleaseDC(mhWnd, mhDC);
    }
}

device_context_handle::operator HDC() const
{
    return mhDC;
}

ピクセルフォーマットの選択



デバイスコンテキストに対してOpenGLがサポートされている状態にするために,ピクセルフォーマットを設定する必要がある.まず,ChoosePixelFormat関数(MSDNのドキュメント)を使って希望のピクセルフォーマットに近いピクセルフォーマットを選択する.
ここで,PIXELFORMATDESCRIPTOR(MSDNのドキュメント)のdwFlagsにPFD_SUPPORT_OPENGLを設定しておくことで,OpenGLが利用できるピクセルフォーマットが選択される.

// 011_choose_pixel_format.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    // ピクセルフォーマットの選択
    PIXELFORMATDESCRIPTOR pfd = { 0 };
    pfd.nSize = sizeof pfd;
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormatIndex = ChoosePixelFormat(hDC, &pfd);
    if(pixelFormatIndex == 0){
        printLastError();
        return false;
    }

    return true;
}

コンパイル

横に長くなってきたが,device_context_handle.cppとGdi32.libが追加されている.
cl /EHsc 011_choose_pixel_format.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib

ピクセルフォーマットの設定

ピクセルフォーマットが選択できたら,そのピクセルフォーマットを設定してやる.設定には,SetPixelFormat関数(MSDNのドキュメント)を用いる.

// 012_set_pixel_format.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    // ピクセルフォーマットの選択
    PIXELFORMATDESCRIPTOR pfd = { 0 };
    pfd.nSize = sizeof pfd;
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormatIndex = ChoosePixelFormat(hDC, &pfd);
    if(pixelFormatIndex == 0){
        printLastError();
        return false;
    }

    // ピクセルフォーマットの設定
    if(!SetPixelFormat(hDC, pixelFormatIndex, &pfd)){
        printLastError();
        return false;
    }

    return true;
}

コンパイル


cl /EHsc 012_set_pixel_format.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib

OpenGLのレンダリングコンテキストの生成



ピクセルフォーマットの設定が終われば,OpenGLのレンダリングコンテキストを生成できる.OpenGLのレンダリングコンテキストは,wglCreateContext関数(MSDNのドキュメント)を呼び出すことで生成できる.作ったレンダリングコンテキストは,wglDeleteContext関数(MSDNのドキュメント)で後始末する.

// 013_create_context.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    /* ピクセルフォーマットの選択から設定まで省略 */

    // OpenGLレンダリングコンテキストの生成
    HGLRC hGLRC = wglCreateContext(hDC);
    if(hGLRC == NULL){
        printLastError();
        return false;
    }

    // OpenGLレンダリングコンテキストの破棄
    if(!wglDeleteContext(hGLRC)){
        printLastError();
        return false;
    }

    return true;
}

コンパイル



とうとうOpenGL32.libというOpenGLのライブラリをリンクするところまできた.

cl /EHsc 013_create_context.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

OpenGLレンダリングコンテキストの設定



OpenGLレンダリングコンテキストの生成が終われば,後はそれを設定するだけ.wglMakeCurrent関数(MSDNのドキュメント)を使う.また,メッセージループが終わった後に,OpenGLレンダリングコンテキストの破棄を行うようにしておく.

// 014_make_current.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    /* 色々省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        / * 省略 */
    }

    // OpenGLレンダリングコンテキストの破棄
    HGLRC hGLRC = wglGetCurrentContext();
    wglMakeCurrent(NULL, NULL):
    wglDeleteContext(hGLRC);

    return msg.wParam;
}

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    /* ピクセルフォーマットの選択から設定まで省略 */

    // OpenGLレンダリングコンテキストの生成
    HGLRC hGLRC = wglCreateContext(hDC);
    if(hGLRC == NULL){
        printLastError();
        return false;
    }

    // OpenGLレンダリングコンテキストの設定
    if(!wglMakeCurrent(hDC, hGLRC)){
        printLastError();
        wglDeleteContext(hGLRC);
        return false;
    }

    return true;
}

コンパイル


cl /EHsc 014_make_current.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

画面のクリア



ようやくOpenGLのAPIを使える状態になったので,OpenGLのヘッダファイルgl/GL.hを追加して,メッセージループでメッセージが来ていない場合に描画を実行する.今回は,glClearColorで色を指定し,glClearにGL_COLOR_BUFFER_BITを指定し,画面を指定した色で塗りつぶす.

// 015_clear.cpp
#include <tchar.h>
#include <Windows.h>
#include <GL/gl.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    /* 色々省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            DispatchMessage(&msg);
        }else{
            glClearColor(1.f, 0.f, 0.f, 1.f);
            glClear(GL_COLOR_BUFFER_BIT);
            glFlush();

            Sleep(100);
        }
    }

    // OpenGLレンダリングコンテキストの破棄
    HGLRC hGLRC = wglGetCurrentContext();
    wglMakeCurrent(NULL, NULL):
    wglDeleteContext(hGLRC);

    return msg.wParam;
}

bool onCreate(HWND hWnd)
{
    /* 省略 */
}

コンパイル


cl /EHsc 015_clear.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

第四回まとめ


  • デバイスコンテキストの取得はGetDC,破棄はReleaseDC
  • OpenGLのレンダリングコンテキストを生成する前に,ChoosePixelFormatでピクセルフォーマットを選択し,SetPixelFormatで設定
  • wglCreateContextでOpenGLレンダリングコンテキストを生成し,wglMakeCurrentで設定

たかが画面をクリアするだけのためにもこれだけの手順が必要な上に,OpenGLそのものを学ぶ上では知らなくても済む内容なので,GLUTやGLEWを使ってしまいたい気持ちがよく分かった.ただ,実際のゲームエンジンなどではライセンスの問題だったり,ゲーム専用ハードはサポートされていないためか,独自に実装していることもあるようなので,知っていて損はないのかなぁ,と.

1 件のコメント: