Windows環境でOpenGLを利用するには、Win32 APIを通じてウィンドウを作成し、デバイスコンテキスト(DC)とレンダリングコンテキスト(RC)を適切に設定する必要があります。以下に、モダンなOpenGL(3.3以降)に対応した実装例を示します。
アーキテクチャの概要
OpenGLの初期化は以下の順序で行います:
- ウィンドウクラスの登録とウィンドウ生成
- デバイスコンテキスト(HDC)の取得
- ピクセルフォーマットの設定
- レンダリングコンテキスト(HGLRC)の作成と紐付け
- 拡張機能のロード(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;
}