Win32 APIによるOpenGLレンダリングコンテキストの構築

Windows環境でOpenGLを利用するには、Win32 APIを通じてウィンドウを作成し、デバイスコンテキスト(DC)とレンダリングコンテキスト(RC)を適切に設定する必要があります。以下に、モダンなOpenGL(3.3以降)に対応した実装例を示します。

アーキテクチャの概要

OpenGLの初期化は以下の順序で行います:

  1. ウィンドウクラスの登録とウィンドウ生成
  2. デバイスコンテキスト(HDC)の取得
  3. ピクセルフォーマットの設定
  4. レンダリングコンテキスト(HGLRC)の作成と紐付け
  5. 拡張機能のロード(GLEWまたはglad)

重要な点として、すべてのOpenGL命令は現在のスレッドにアクティブ化されたRCに対して発行されます。wglMakeCurrentを呼び出す前にOpenGL関数を使用すると、未定義動作またはアクセス違反が発生します。

実装コード

レンダリングコンテキスト管理クラス

#pragma once
#include <windows.h>
#include <GL/glew.h>
#include <cstdio>

class RenderContext
{
public:
    RenderContext();
    ~RenderContext();
    
    bool Initialize(HWND windowHandle);
    void Release();
    void MakeCurrent();
    void SwapBuffers();

private:
    bool ConfigurePixelFormat(HDC deviceContext);
    
    HWND m_hwnd;
    HDC  m_hdc;
    HGLRC m_hglrc;
    int  m_pixelFormat;
};

RenderContext::RenderContext()
    : m_hwnd(nullptr)
    , m_hdc(nullptr)
    , m_hglrc(nullptr)
    , m_pixelFormat(0)
{
}

RenderContext::~RenderContext()
{
    Release();
}

bool RenderContext::ConfigurePixelFormat(HDC deviceContext)
{
    PIXELFORMATDESCRIPTOR pfdDesc = {};
    pfdDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfdDesc.nVersion = 1;
    pfdDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfdDesc.iPixelType = PFD_TYPE_RGBA;
    pfdDesc.cColorBits = 24;
    pfdDesc.cAlphaBits = 8;
    pfdDesc.cDepthBits = 24;
    pfdDesc.cStencilBits = 8;
    pfdDesc.iLayerType = PFD_MAIN_PLANE;

    int suggestedFormat = ChoosePixelFormat(deviceContext, &pfdDesc);
    if (suggestedFormat == 0) {
        return false;
    }

    if (!SetPixelFormat(deviceContext, suggestedFormat, &pfdDesc)) {
        return false;
    }
    
    m_pixelFormat = suggestedFormat;
    return true;
}

bool RenderContext::Initialize(HWND windowHandle)
{
    m_hwnd = windowHandle;
    m_hdc = GetDC(m_hwnd);
    
    if (!ConfigurePixelFormat(m_hdc)) {
        return false;
    }

    // 従来のコンテキスト作成(後で拡張可能)
    m_hglrc = wglCreateContext(m_hdc);
    if (!m_hglrc) {
        return false;
    }

    MakeCurrent();

    // GLEWの初期化
    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK) {
        return false;
    }

    // バージョン情報の出力
    const GLubyte* glVersion = glGetString(GL_VERSION);
    const GLubyte* glVendor = glGetString(GL_VENDOR);
    const GLubyte* glRenderer = glGetString(GL_RENDERER);
    
    fprintf(stderr, "OpenGL Version: %s\n", glVersion);
    fprintf(stderr, "Vendor: %s\n", glVendor);
    fprintf(stderr, "Renderer: %s\n", glRenderer);

    return true;
}

void RenderContext::Release()
{
    if (m_hglrc) {
        wglMakeCurrent(nullptr, nullptr);
        wglDeleteContext(m_hglrc);
        m_hglrc = nullptr;
    }
    if (m_hwnd && m_hdc) {
        ReleaseDC(m_hwnd, m_hdc);
        m_hdc = nullptr;
    }
}

void RenderContext::MakeCurrent()
{
    wglMakeCurrent(m_hdc, m_hglrc);
}

void RenderContext::SwapBuffers()
{
    ::SwapBuffers(m_hdc);
}

アプリケーションフレームワーク

#include <windows.h>
#include <functional>
#include "RenderContext.h"

class ApplicationWindow
{
public:
    using RenderCallback = std::function<void()>;

    ApplicationWindow();
    ~ApplicationWindow();

    bool Create(const wchar_t* title, int width, int height);
    void SetRenderCallback(RenderCallback callback);
    void ExecuteMainLoop();

private:
    static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, 
                                            WPARAM wParam, LPARAM lParam);
    bool ProcessWindowMessages();

    RenderCallback m_renderFunc;
    RenderContext m_context;
    
    HWND m_windowHandle;
    HINSTANCE m_instance;
    int m_clientWidth;
    int m_clientHeight;
    bool m_running;
};

ApplicationWindow::ApplicationWindow()
    : m_windowHandle(nullptr)
    , m_instance(GetModuleHandle(nullptr))
    , m_clientWidth(1280)
    , m_clientHeight(720)
    , m_running(false)
{
}

ApplicationWindow::~ApplicationWindow()
{
    if (m_windowHandle) {
        DestroyWindow(m_windowHandle);
    }
}

bool ApplicationWindow::Create(const wchar_t* title, int width, int height)
{
    m_clientWidth = width;
    m_clientHeight = height;

    WNDCLASSEXW wndClass = {};
    wndClass.cbSize = sizeof(WNDCLASSEXW);
    wndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WindowProcedure;
    wndClass.hInstance = m_instance;
    wndClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wndClass.lpszClassName = L"OpenGLApplication";

    if (!RegisterClassExW(&wndClass)) {
        return false;
    }

    RECT rect = { 0, 0, width, height };
    AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW);

    m_windowHandle = CreateWindowExW(
        WS_EX_APPWINDOW,
        L"OpenGLApplication",
        title,
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        CW_USEDEFAULT, CW_USEDEFAULT,
        rect.right - rect.left,
        rect.bottom - rect.top,
        nullptr, nullptr,
        m_instance,
        this  // ユーザーデータとしてthisポインタを渡す
    );

    if (!m_windowHandle) {
        return false;
    }

    SetWindowLongPtr(m_windowHandle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));

    if (!m_context.Initialize(m_windowHandle)) {
        return false;
    }

    ShowWindow(m_windowHandle, SW_SHOW);
    UpdateWindow(m_windowHandle);
    m_running = true;

    return true;
}

void ApplicationWindow::SetRenderCallback(RenderCallback callback)
{
    m_renderFunc = std::move(callback);
}

void ApplicationWindow::ExecuteMainLoop()
{
    while (m_running) {
        if (ProcessWindowMessages()) {
            break;
        }

        if (m_renderFunc) {
            m_renderFunc();
        }

        m_context.SwapBuffers();
    }
}

bool ApplicationWindow::ProcessWindowMessages()
{
    MSG message;
    while (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
        if (message.message == WM_QUIT) {
            return true;
        }
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return false;
}

LRESULT CALLBACK ApplicationWindow::WindowProcedure(HWND hwnd, UINT msg, 
                                                   WPARAM wParam, LPARAM lParam)
{
    auto* app = reinterpret_cast<ApplicationWindow*>(
        GetWindowLongPtr(hwnd, GWLP_USERDATA));

    switch (msg) {
        case WM_CREATE:
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 0;

        case WM_SIZE:
            if (app) {
                app->m_clientWidth = LOWORD(lParam);
                app->m_clientHeight = HIWORD(lParam);
                glViewport(0, 0, app->m_clientWidth, app->m_clientHeight);
            }
            return 0;

        case WM_KEYDOWN:
            if (wParam == VK_ESCAPE) {
                PostQuitMessage(0);
            }
            return 0;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}

エントリーポイント

#include <windows.h>
#include <GL/glew.h>

// 前方宣言
class ApplicationWindow;

// シーン管理(ユーザー実装)
struct SceneData {
    float clearColor[4] = { 0.2f, 0.3f, 0.3f, 1.0f };
    float rotationAngle = 0.0f;
};

void InitializeGraphics();
void RenderFrame(const SceneData& scene);

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
    ApplicationWindow app;
    
    SceneData scene;

    if (!app.Create(L"Modern OpenGL Application", 1280, 720)) {
        return -1;
    }

    InitializeGraphics();

    app.SetRenderCallback([&scene]() {
        glClearColor(scene.clearColor[0], scene.clearColor[1],
                     scene.clearColor[2], scene.clearColor[3]);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        RenderFrame(scene);
    });

    app.ExecuteMainLoop();
    return 0;
}

void InitializeGraphics()
{
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
}

void RenderFrame(const SceneData& scene)
{
    // ユーザー定義の描画処理
    // シェーダー、VAO、VBOを使用したモダンOpenGL描画
}

注意点と拡張

上記の実装はOpenGL 3.3以降のコアプロファイルを想定しています。より新しいバージョン(4.x)を使用する場合、または特定の機能(デバッグコンテキスト、マルチサンプリングなど)が必要な場合は、wglCreateContextAttribsARBを使用して拡張コンテキストを作成することを検討してください。

また、複数のGPUがある環境では、NVidia OptimusまたはAMD PowerXpressに対応するために、エクスポートシンボルの追加が必要になる場合があります:

extern "C" {
    __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}

タグ: Win32 API OpenGL GLEW Windowsプログラミング GLSL

10月11日 03:31 投稿