Skip to content

Instantly share code, notes, and snippets.

@nickrolfe
Last active February 20, 2024 20:11
Show Gist options
  • Star 61 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save nickrolfe/1127313ed1dbf80254b614a721b3ee9c to your computer and use it in GitHub Desktop.
Save nickrolfe/1127313ed1dbf80254b614a721b3ee9c to your computer and use it in GitHub Desktop.
Sample code showing how to create a window using a modern OpenGL core profile context without any libraries other than the standard Win32 wglXXX calls.
// Sample code showing how to create a modern OpenGL window and rendering context on Win32.
#include <windows.h>
#include <gl/gl.h>
#include <stdbool.h>
typedef HGLRC WINAPI wglCreateContextAttribsARB_type(HDC hdc, HGLRC hShareContext,
const int *attribList);
wglCreateContextAttribsARB_type *wglCreateContextAttribsARB;
// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt for all values
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
typedef BOOL WINAPI wglChoosePixelFormatARB_type(HDC hdc, const int *piAttribIList,
const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats);
wglChoosePixelFormatARB_type *wglChoosePixelFormatARB;
// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt for all values
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
#define WGL_ACCELERATION_ARB 0x2003
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#define WGL_PIXEL_TYPE_ARB 0x2013
#define WGL_COLOR_BITS_ARB 0x2014
#define WGL_DEPTH_BITS_ARB 0x2022
#define WGL_STENCIL_BITS_ARB 0x2023
#define WGL_FULL_ACCELERATION_ARB 0x2027
#define WGL_TYPE_RGBA_ARB 0x202B
static void
fatal_error(char *msg)
{
MessageBoxA(NULL, msg, "Error", MB_OK | MB_ICONEXCLAMATION);
exit(EXIT_FAILURE);
}
static void
init_opengl_extensions(void)
{
// Before we can load extensions, we need a dummy OpenGL context, created using a dummy window.
// We use a dummy window because you can only set the pixel format for a window once. For the
// real window, we want to use wglChoosePixelFormatARB (so we can potentially specify options
// that aren't available in PIXELFORMATDESCRIPTOR), but we can't load and use that before we
// have a context.
WNDCLASSA window_class = {
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = DefWindowProcA,
.hInstance = GetModuleHandle(0),
.lpszClassName = "Dummy_WGL_djuasiodwa",
};
if (!RegisterClassA(&window_class)) {
fatal_error("Failed to register dummy OpenGL window.");
}
HWND dummy_window = CreateWindowExA(
0,
window_class.lpszClassName,
"Dummy OpenGL Window",
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
window_class.hInstance,
0);
if (!dummy_window) {
fatal_error("Failed to create dummy OpenGL window.");
}
HDC dummy_dc = GetDC(dummy_window);
PIXELFORMATDESCRIPTOR pfd = {
.nSize = sizeof(pfd),
.nVersion = 1,
.iPixelType = PFD_TYPE_RGBA,
.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
.cColorBits = 32,
.cAlphaBits = 8,
.iLayerType = PFD_MAIN_PLANE,
.cDepthBits = 24,
.cStencilBits = 8,
};
int pixel_format = ChoosePixelFormat(dummy_dc, &pfd);
if (!pixel_format) {
fatal_error("Failed to find a suitable pixel format.");
}
if (!SetPixelFormat(dummy_dc, pixel_format, &pfd)) {
fatal_error("Failed to set the pixel format.");
}
HGLRC dummy_context = wglCreateContext(dummy_dc);
if (!dummy_context) {
fatal_error("Failed to create a dummy OpenGL rendering context.");
}
if (!wglMakeCurrent(dummy_dc, dummy_context)) {
fatal_error("Failed to activate dummy OpenGL rendering context.");
}
wglCreateContextAttribsARB = (wglCreateContextAttribsARB_type*)wglGetProcAddress(
"wglCreateContextAttribsARB");
wglChoosePixelFormatARB = (wglChoosePixelFormatARB_type*)wglGetProcAddress(
"wglChoosePixelFormatARB");
wglMakeCurrent(dummy_dc, 0);
wglDeleteContext(dummy_context);
ReleaseDC(dummy_window, dummy_dc);
DestroyWindow(dummy_window);
}
static HGLRC
init_opengl(HDC real_dc)
{
init_opengl_extensions();
// Now we can choose a pixel format the modern way, using wglChoosePixelFormatARB.
int pixel_format_attribs[] = {
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
0
};
int pixel_format;
UINT num_formats;
wglChoosePixelFormatARB(real_dc, pixel_format_attribs, 0, 1, &pixel_format, &num_formats);
if (!num_formats) {
fatal_error("Failed to set the OpenGL 3.3 pixel format.");
}
PIXELFORMATDESCRIPTOR pfd;
DescribePixelFormat(real_dc, pixel_format, sizeof(pfd), &pfd);
if (!SetPixelFormat(real_dc, pixel_format, &pfd)) {
fatal_error("Failed to set the OpenGL 3.3 pixel format.");
}
// Specify that we want to create an OpenGL 3.3 core profile context
int gl33_attribs[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0,
};
HGLRC gl33_context = wglCreateContextAttribsARB(real_dc, 0, gl33_attribs);
if (!gl33_context) {
fatal_error("Failed to create OpenGL 3.3 context.");
}
if (!wglMakeCurrent(real_dc, gl33_context)) {
fatal_error("Failed to activate OpenGL 3.3 rendering context.");
}
return gl33_context;
}
static LRESULT CALLBACK
window_callback(HWND window, UINT msg, WPARAM wparam, LPARAM lparam)
{
LRESULT result = 0;
switch (msg) {
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
result = DefWindowProcA(window, msg, wparam, lparam);
break;
}
return result;
}
static HWND
create_window(HINSTANCE inst)
{
WNDCLASSA window_class = {
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = window_callback,
.hInstance = inst,
.hCursor = LoadCursor(0, IDC_ARROW),
.hbrBackground = 0,
.lpszClassName = "WGL_fdjhsklf",
};
if (!RegisterClassA(&window_class)) {
fatal_error("Failed to register window.");
}
// Specify a desired width and height, then adjust the rect so the window's client area will be
// that size.
RECT rect = {
.right = 1024,
.bottom = 576,
};
DWORD window_style = WS_OVERLAPPEDWINDOW;
AdjustWindowRect(&rect, window_style, false);
HWND window = CreateWindowExA(
0,
window_class.lpszClassName,
"OpenGL",
window_style,
CW_USEDEFAULT,
CW_USEDEFAULT,
rect.right - rect.left,
rect.bottom - rect.top,
0,
0,
inst,
0);
if (!window) {
fatal_error("Failed to create window.");
}
return window;
}
int WINAPI
WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd_line, int show)
{
HWND window = create_window(inst);
HDC gldc = GetDC(window);
HGLRC glrc = init_opengl(gldc);
ShowWindow(window, show);
UpdateWindow(window);
bool running = true;
while (running) {
MSG msg;
while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
running = false;
} else {
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
}
glClearColor(1.0f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Do OpenGL rendering here
SwapBuffers(gldc);
}
return 0;
}
@redoak
Copy link

redoak commented Mar 13, 2020

Do you know if CS_OWNDC is recommended in all scenarios or only in some? I'm unable to find reliable information on this. It seems to have been highly recommended in the past but might not matter anymore.

@nickrolfe
Copy link
Author

I'm sorry, it's been a few years since I was looking at this stuff. I vaguely remember things not working correctly without CS_OWNDC, but I can't be sure, and I'd have to go Googling to try and remember what the flag is supposed to do exactly.

@flysand7
Copy link

flysand7 commented Nov 5, 2020

one of the tutorials on Khronos here says that OWNDC has to be there

@tilkinsc
Copy link

tilkinsc commented Jan 14, 2021

It's legacy support implementations of windows (no later than win98 or win95) to use the CS_OWNDC. Windows had the option of sharing DC between processes which could result in poor preformance. This feature has been deprecated.

@pranavgore
Copy link

pranavgore commented Sep 9, 2021

@nickrolfe
Hello, I tried running this code. Although I modified it a little bit to make it work with GLEW and WGLEW
But I am facing one issue.
wglChoosePixelFormatARB is not returning any pixel format or numFormat. I am not able to understand what is going wrong. Kindly please help. Also, let me know if I am doing it in the wrong way, or have missed out on something. Attaching the code file for reference. Download the file and change the extension to.cpp

MainWindow
library.

@tilkinsc
Copy link

tilkinsc commented Sep 10, 2021

Hello @pranavgore , your image didn't seem to work. Please dump the code
```cpp
like so
```

@pranavgore
Copy link

pranavgore commented Sep 11, 2021

Hello @pranavgore , your image didn't seem to work. Please dump the code

like so

Hello, @tilkinsc thanks for the reply! Please find below the code

#include <Windows.h>
#include <stdio.h>
#include <iostream>

#include <GL\glew.h>
#include <GL\wglew.h>
#include <GL\GL.h>

#define WIN_WIDTH	800
#define WIN_HEIGHT	600

BOOL gbEscapeKeyIsPressed = FALSE;
BOOL gbIsFullscreen = FALSE;
BOOL gbActiveWindow = FALSE;
BOOL gbDone = FALSE;

HWND ghWnd = NULL;
HDC ghDC = NULL;
HGLRC ghGLRC = NULL;
DWORD dwStyle;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
WINDOWPLACEMENT wpPrev = { sizeof(WINDOWPLACEMENT) };

void Initialize(void);
void Display(void);
void Uninitialize(void);
void Resize(int Width, int Height);

HWND CreateApplicationWindow(HINSTANCE hInstance);
void InitializeOpenGLExtensions();
HGLRC InitializeOpenGL(HDC hDC);

//main function
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
	// Open the console.
	if (AllocConsole())
	{
		FILE* fDummy;

		freopen_s(&fDummy, "CONOUT$", "wt", stdout);
		freopen_s(&fDummy, "CONIN$", "wt", stderr);
		freopen_s(&fDummy, "CONIN$", "rt", stdin);
		SetConsoleTitle(L"Debug Console");

		std::ios::sync_with_stdio(1);
	}

	std::cout << "Initializing the application." << std::endl;

	HWND hWnd = CreateApplicationWindow(hInstance);
	HDC hDC = GetDC(hWnd);
	HGLRC hGLRC = InitializeOpenGL(hDC);

	//display created window and update window
	ShowWindow(hWnd, nCmdShow);
	SetForegroundWindow(hWnd);
	SetFocus(hWnd);

	MSG msg;
	// Game Loop
	while (gbDone == FALSE)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				gbDone = TRUE;
			else
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		else
		{
			Display();
			if (gbActiveWindow == TRUE)
			{
				if (gbEscapeKeyIsPressed == TRUE)
					gbDone = TRUE;
			}
		}
	}

	//Uninitialize();
	return((int)msg.wParam);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	void ToggleFullscreen(void);

	switch (iMsg)
	{
	case WM_ACTIVATE:
		if (HIWORD(wParam) == 0)
			gbActiveWindow = TRUE;
		else
			gbActiveWindow = FALSE;
		break;

	case WM_SIZE:
		Resize(LOWORD(lParam), HIWORD(lParam));
		break;

	case WM_CREATE:
		break;

	case WM_CLOSE:
	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	case WM_KEYDOWN:
		switch (wParam)
		{
		case VK_ESCAPE:
			DestroyWindow(hWnd);
			break;

		case 0x46:	//F key
			if (gbIsFullscreen == FALSE)
			{
				ToggleFullscreen();
				gbIsFullscreen = TRUE;
			}
			else
			{
				ToggleFullscreen();
				gbIsFullscreen = FALSE;
			}
			break;

		default:
			break;
		}
		break;

	default:
		break;
	}

	return(DefWindowProc(hWnd, iMsg, wParam, lParam));
}

void ToggleFullscreen(void)
{
	HMONITOR hMonitor;
	MONITORINFO mi;
	BOOL bWindowPlacement;
	BOOL bMonitorInfo;

	if (gbIsFullscreen == FALSE)
	{
		dwStyle = GetWindowLong(ghWnd, GWL_STYLE);
		if (dwStyle & WS_OVERLAPPEDWINDOW)
		{
			mi.cbSize = { sizeof(MONITORINFO) };

			bWindowPlacement = GetWindowPlacement(ghWnd, &wpPrev);
			hMonitor = MonitorFromWindow(ghWnd, MONITOR_DEFAULTTOPRIMARY);
			bMonitorInfo = GetMonitorInfo(hMonitor, &mi);

			if (bWindowPlacement == TRUE && bMonitorInfo == TRUE)
			{
				SetWindowLong(ghWnd, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW);
				SetWindowPos(ghWnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_NOZORDER | SWP_FRAMECHANGED);
			}
		}
	}
	else
	{
		SetWindowLong(ghWnd, GWL_STYLE, dwStyle | WS_OVERLAPPEDWINDOW);
		SetWindowPlacement(ghWnd, &wpPrev);
		SetWindowPos(ghWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_FRAMECHANGED);
	}
}

void Initialize(void)
{
	PIXELFORMATDESCRIPTOR pfd;
	INT iPixelFormatIndex;

	ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));

	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;
	pfd.cRedBits = 8;
	pfd.cGreenBits = 8;
	pfd.cBlueBits = 8;
	pfd.cAlphaBits = 8;
	pfd.cDepthBits = 32;

	ghDC = GetDC(ghWnd);

	iPixelFormatIndex = ChoosePixelFormat(ghDC, &pfd);

	if (iPixelFormatIndex == 0)
	{
		ReleaseDC(ghWnd, ghDC);
		ghDC = NULL;
	}

	if (SetPixelFormat(ghDC, iPixelFormatIndex, &pfd) == FALSE)
	{
		ReleaseDC(ghWnd, ghDC);
		ghDC = NULL;
	}

	ghGLRC = wglCreateContext(ghDC);
	if (ghGLRC == NULL)
	{
		ReleaseDC(ghWnd, ghDC);
		ghDC = NULL;
	}

	if (wglMakeCurrent(ghDC, ghGLRC) == FALSE)
	{
		wglDeleteContext(ghGLRC);
		ghGLRC = NULL;
		ReleaseDC(ghWnd, ghDC);
		ghDC = NULL;
	}

	glClearColor(0.0f, 0.0f, 1.0f, 0.0f);

	//resize(WIN_WIDTH, WIN_HEIGHT);
}

void Display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	SwapBuffers(ghDC);
}

void Resize(int Width, int Height)
{
	if (Height == 0)
		Height = 1;

	glViewport(0, 0, (GLsizei)Width, (GLsizei)Height);
}

void Uninitialize(void)
{
	if (gbIsFullscreen == TRUE)
	{
		dwStyle = GetWindowLong(ghWnd, GWL_STYLE);
		SetWindowLong(ghWnd, GWL_STYLE, dwStyle | WS_OVERLAPPEDWINDOW);
		SetWindowPlacement(ghWnd, &wpPrev);
		SetWindowPos(ghWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
	}

	wglMakeCurrent(NULL, NULL);

	wglDeleteContext(ghGLRC);
	ghGLRC = NULL;

	ReleaseDC(ghWnd, ghDC);
	ghDC = NULL;

	DestroyWindow(ghWnd);
}

HWND CreateApplicationWindow(HINSTANCE hInstance)
{
	WNDCLASSEX WndClassEx;

	WndClassEx.cbClsExtra = 0;
	WndClassEx.cbSize = sizeof(WNDCLASSEX);
	WndClassEx.cbWndExtra = 0;
	WndClassEx.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	WndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
	WndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	WndClassEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	WndClassEx.hInstance = hInstance;
	WndClassEx.lpfnWndProc = WndProc;
	WndClassEx.lpszClassName = TEXT("OpenGL");
	WndClassEx.lpszMenuName = NULL;
	WndClassEx.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

	if (!RegisterClassEx(&WndClassEx))
	{
		std::cout << "Failed to register the window class." << std::endl;
	}

	UINT uiWidth = GetSystemMetrics(SM_CXSCREEN);
	UINT uiHeight = GetSystemMetrics(SM_CYSCREEN);

	UINT uiXFinalPosition = (uiWidth / 2) - (WIN_WIDTH / 2);
	UINT uiYFinalPosition = (uiHeight / 2) - (WIN_HEIGHT / 2);

	HWND hWnd = CreateWindowEx(
		WS_EX_APPWINDOW, 
		WndClassEx.lpszClassName,
		TEXT("OpenGL Blue Window"), 
		WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 
		uiXFinalPosition, 
		uiYFinalPosition, 
		WIN_WIDTH, 
		WIN_HEIGHT, 
		NULL, 
		NULL, 
		hInstance, 
		NULL);

	if (!hWnd)
	{
		std::cout << "Failed to create the application window." << std::endl;
	}

	return hWnd;
}

void InitializeOpenGLExtensions()
{
	// Before we can load extensions, we need a dummy OpenGL context, created using dummy window.
	// We use a dummy window because the pixel format can only be set for a window once.
	// For the real window, we want to use wglChoosePixelFormatARB which has bit more options,
	// than traditional PIXELFORMATDESCRIPTOR, but this cannot be loaded and used that before we have a context.

	WNDCLASSEX dummyWndClassEx;

	dummyWndClassEx.cbClsExtra = 0;
	dummyWndClassEx.cbSize = sizeof(WNDCLASSEX);
	dummyWndClassEx.cbWndExtra = 0;
	dummyWndClassEx.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	dummyWndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
	dummyWndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	dummyWndClassEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	dummyWndClassEx.hInstance = GetModuleHandle(0);
	dummyWndClassEx.lpfnWndProc = DefWindowProc;
	dummyWndClassEx.lpszClassName = TEXT("OpenGLDummy");
	dummyWndClassEx.lpszMenuName = NULL;
	dummyWndClassEx.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

	if (!RegisterClassEx(&dummyWndClassEx))
	{
		std::cout << "Failed to register the dummy window class." << std::endl;
	}

	HWND dummyHwnd = CreateWindowEx(
		0,
		dummyWndClassEx.lpszClassName,
		TEXT("DummyWindow"),
		0,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		0,
		0,
		dummyWndClassEx.hInstance,
		0);

	if (!dummyHwnd)
	{
		std::cout << "Failed to create the dummy application window." << std::endl;
	}

	HDC DummyDC = GetDC(dummyHwnd);

	PIXELFORMATDESCRIPTOR pfd;

	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;
	pfd.cRedBits = 8;
	pfd.cGreenBits = 8;
	pfd.cBlueBits = 8;
	pfd.cAlphaBits = 8;
	pfd.cDepthBits = 24;
	pfd.iLayerType = PFD_MAIN_PLANE;

	int PixelFormat = ChoosePixelFormat(DummyDC, &pfd);
	if (!PixelFormat)
	{
		std::cout << "Failed to find a suitable pixel format." << std::endl;
	}

	if (!SetPixelFormat(DummyDC, PixelFormat, &pfd))
	{
		std::cout << "Failed to set the pixel format." << std::endl;
	}

	HGLRC dummyHglrc = wglCreateContext(DummyDC);
	if (!dummyHglrc)
	{
		std::cout << "Failed to create the dummy OpenGL rendering context." << std::endl;
	}

	if (!wglMakeCurrent(DummyDC, dummyHglrc))
	{
		std::cout << "Failed to set the dummy OpenGL rendering context." << std::endl;
	}

	wglMakeCurrent(DummyDC, 0);
	wglDeleteContext(dummyHglrc);
	ReleaseDC(dummyHwnd, DummyDC);
	DestroyWindow(dummyHwnd);
}

HGLRC InitializeOpenGL(HDC hDC)
{
	InitializeOpenGLExtensions();

	// Now we can choose a pixel format the modern way, using wglChoosePixelFormatARB
	int PixelFormatAttribs[] = {
		WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
		WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
		WGL_DOUBLE_BUFFER_ARB,	GL_TRUE,
		WGL_ACCELERATION_ARB,	WGL_FULL_ACCELERATION_ARB,
		WGL_PIXEL_TYPE_ARB,		WGL_TYPE_RGBA_ARB,
		WGL_COLOR_BITS_ARB,		32,
		WGL_DEPTH_BITS_ARB,		24,
		//WGL_ALPHA_BITS_ARB,		8,
		WGL_STENCIL_BITS_ARB,	8,
		//WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
		//WGL_SAMPLES_ARB, 4,
		0
	};

	int pixelFormat = 0;
	UINT NumFormats = 0;

	wglChoosePixelFormatARB(hDC, PixelFormatAttribs, NULL, 1, &pixelFormat, &NumFormats);
	if (NumFormats == 0)
	{
		std::cout << "Failed to choose pixel format." << std::endl;
	}

	PIXELFORMATDESCRIPTOR pfd;
	DescribePixelFormat(hDC, pixelFormat, sizeof(pfd), &pfd);
	if (!SetPixelFormat(hDC, pixelFormat, &pfd))
	{
		std::cout << "Failed to set pixel format." << std::endl;
	}

	// Specify that we want to create an OpenGL 4.6 core profile context.
	int gl46Attribs[] = {
		WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
		WGL_CONTEXT_MINOR_VERSION_ARB, 6,
		WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
		0
	};

	HGLRC gl46Context = wglCreateContextAttribsARB(hDC, 0, gl46Attribs);
	if (!gl46Context)
	{
		std::cout << "Failed to create OpenGL 4.6 context." << std::endl;
	}

	if (!wglMakeCurrent(hDC, gl46Context))
	{
		std::cout << "Failed to activate OpenGL 4.6 rendering context." << std::endl;
	}

	return gl46Context;
}

@tilkinsc
Copy link

First off, before I start: I don't really care how credible you are as a programmer or your experience with windows programming.

WGL on its own requires extensive research and practice. There are so many things that you need to know in order to produce valid code. For example, you need to wait until WM_CREATE is processed for setting the pixel format as well as getting the DC. There is also by byproducts of going this route, such as having to program the timer yourself if not using vsync, window functions such as fullscreen/borderless, a new thread has to be used because windows will pause your thread when moving the window by getting stuck in DefWindowProc(), you need a dummy context to load some WGL functions to even use WGL, functions are very tightly packed and one wrong ordering could mean failure, in order to get multisampling you will have to use a separate pixelformat algorithm and 'discover' that with windows, windows cant have pixel formats reset to them so you have to create an entirely new window, and more. Such knowledge comes from hours of study and implementation practice, searching fishy forums, etc.

This is not a slapstick project where you can just take someone else's code like above and rename and break the functionality until it works.

I can tell you that you don't have a pixelformat up front due to not handling WM_CREATE in WndProc. I can also tell you that you never called half the functions in your code, which contain important steps. I think you are better off forgetting everything you know about WGL because the original source above probably won't even work today. Whatever you may have cobbled is likely mislead beliefs. Your document that you showed has more bugs than I have attention span, so I didn't look into it too deep. Just deep enough to know that you lack understanding of the process and prerequisites. Which is okay, everyone has to learn something before they know said something.

So I took several hours of my time to cobble this. I can answer further questions there.
https://gist.github.com/tilkinsc/e9ecf0e1653df40afdb9d62ff6d7b5cc

@pranavgore
Copy link

Hello @tilkinsc
Thanks for the prompt reply. First of all, I want to clear that, as you have said, I am relatively very new to OpenGL and Win32 programming and do not have much experience in it. And not getting any good sources to learn apart from OpenGL programming guide and Opengl superbible. Upon looking into the code you have sent, I realized that I have made numerous mistakes and dummy window and context creation, as well as real window and context creation, is not in order. I indeed need to learn a lot before starting to work with WGL. I'll definitely go through the documentation you have mentioned throughout the code and rewrite my code.
Thanks for the valuable guidance and code sample.

@tilkinsc
Copy link

Let this just be a good lesson on why we depend so greatly on GLFW, SDL2, Qt, etc.

@tilkinsc
Copy link

tilkinsc commented Sep 13, 2021

@pranavgore a little dated, but you can use this. It introduces a few flaws you will have to unlearn and you have to translate it to c++, but yeah. https://www.youtube.com/watch?v=VS8wlS9hF8E

If anyone wants to pay me to do opengl tutorials on the weekends I'd be down.

@binarybotany
Copy link

Link in the comments on line 11 is dead, here is the new one:
https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt

@tilkinsc
Copy link

tilkinsc commented Dec 1, 2021

Was this @ me?

@Immanuel-C
Copy link

Thanks man I was trying to find something like this : )

@NTimmons
Copy link

Link in the comments on line 11 is dead, here is the new one: https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt

Just came here to say this 😂 haha

Thanks for the reference Nick!

@NTimmons
Copy link

NTimmons commented Apr 16, 2022

Here is a version of the above code without the option to specify OpenGL version.
It simplifies the code a lot and runs on Windows 10, compiling with MSVC v142 (VS2019) with /std:c++17 set.

Current output on my machine:

GL_VENDOR: NVIDIA Corporation
GL_RENDERER: NVIDIA GeForce GTX 1080/PCIe/SSE2
GL_VERSION: 4.6.0 NVIDIA 497.29
#include <windows.h>
#include <GL/gl.h>

#include <iostream>

// Updated and simplified from nickrolfe's gist: https://gist.github.com/nickrolfe/1127313ed1dbf80254b614a721b3ee9c


static LRESULT CALLBACK WindowCallbackFn(HWND window, UINT msg, WPARAM wparam, LPARAM lparam)
{
    LRESULT res = 0;

    switch (msg) 
    {
        case WM_CLOSE:
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            res = DefWindowProcA(window, msg, wparam, lparam);
            break;
    }

    return res;
}

static HWND CreateWindowInstance(HINSTANCE _inst, int _width, int _height)
{
    WNDCLASSA windowClass = { 0 };
    windowClass.style           = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    windowClass.lpfnWndProc     = WindowCallbackFn;
    windowClass.hInstance       = _inst;
    windowClass.hCursor         = LoadCursor(0, IDC_ARROW);
    windowClass.hbrBackground   = 0;
    windowClass.lpszClassName   = "GLWindow";

    if (!RegisterClassA(&windowClass))
    {
        DWORD errCode = GetLastError();
        std::cout << "Failed to register window. Err:" << errCode << "\n" ;
    }

    RECT rect;
    rect.left   = 0;
    rect.top    = 0;
    rect.right  = _width;
    rect.bottom = _height;
 
    DWORD windowStyle = WS_OVERLAPPEDWINDOW;
    AdjustWindowRect(&rect, windowStyle, false);

    HWND window = CreateWindowExA(
        0,
        windowClass.lpszClassName,
        "OpenGL",
        windowStyle,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        rect.right - rect.left,
        rect.bottom - rect.top,
        0,
        0,
        _inst,
        0);

    if (!window) 
    {
        std::cout << "Failed to create window.\n";
    }

    return window;
}

HGLRC InitOpenGL(HWND _window)
{
    HDC winHDC = GetDC(_window);

    PIXELFORMATDESCRIPTOR windowPixelFormatDesc = { 0 };
    windowPixelFormatDesc.nSize         = sizeof(windowPixelFormatDesc);
    windowPixelFormatDesc.nVersion      = 1;
    windowPixelFormatDesc.iPixelType    = PFD_TYPE_RGBA;
    windowPixelFormatDesc.dwFlags       = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    windowPixelFormatDesc.cColorBits    = 32;
    windowPixelFormatDesc.cAlphaBits    = 8;
    windowPixelFormatDesc.iLayerType    = PFD_MAIN_PLANE;
    windowPixelFormatDesc.cDepthBits    = 24;
    windowPixelFormatDesc.cStencilBits  = 8;

    int pixelFormat = ChoosePixelFormat(winHDC, &windowPixelFormatDesc);
    if (!pixelFormat)
    {
        std::cout << "Unable to find a suitable pixel format for the requested description.\n";
        return (HGLRC)0;
    }
    
    if (!SetPixelFormat(winHDC, pixelFormat, &windowPixelFormatDesc)) 
    {
        std::cout << "Unable to set the pixel format.\n";
    }

    HGLRC glContext = wglCreateContext(winHDC);
    if (!glContext)
    {
        std::cout << "Unable to create OpenGL context.\n";
    }

    if (!wglMakeCurrent(winHDC, glContext)) {
        std::cout << "Unable to apply OpenGL context to window.\n";

    }

    return glContext;
}

void PrintGLVersionInformation()
{
    std::cout<< "GL_VENDOR: "       << glGetString(GL_VENDOR)       << "\n";
    std::cout<< "GL_RENDERER: "     << glGetString(GL_RENDERER)     << "\n";
    std::cout<< "GL_VERSION: "      << glGetString(GL_VERSION)      << "\n";
    // Uncomment for long string...
    //std::cout<< "GL_EXTENSIONS: "   << glGetString(GL_EXTENSIONS)   << "\n";
}

int main()
{
	HINSTANCE   hInst           = GetModuleHandle(0);
    HWND        windowInstance  = CreateWindowInstance(hInst, 1024, 1024);
    HDC         winHDC          = GetDC(windowInstance);
    HGLRC       glContext       = InitOpenGL(windowInstance);


    PrintGLVersionInformation();

    ShowWindow(windowInstance, SW_SHOW);
    UpdateWindow(windowInstance);

    bool running = true;
    while (running)
    {
        MSG msg;
        while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) 
        {
            if (msg.message == WM_QUIT) 
            {
                running = false;
            }
            else 
            {
                TranslateMessage(&msg);
                DispatchMessageA(&msg);
            }
        }

        glClearColor(0.6f, 0.7f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        SwapBuffers(winHDC);
    }

    return 0;
}

@tilkinsc
Copy link

No. The above of code lacks a proper context with a proper pixel format. If you are going as low as WGL and gdi32+user32 libs, you should create a full context. https://gist.github.com/tilkinsc/e9ecf0e1653df40afdb9d62ff6d7b5cc

@NTimmons
Copy link

What is improper about the context and pixel format?
The context is valid, the pixel format is valid, therefore I am able to render geometry.

Is this invalid for some other, more complex use-cases?

@tilkinsc
Copy link

The context you are creating could very well be hardware accelerated. You are able to get the function pointers and call functions. The context you create initially is as good as to load said functions. You will find a lot of things about contexts and their attributes are 'implementation defined' which will inevitably be worse for some users and result in unexpected behaviors (not in the C sense, but literally). Make it a point to explicitly define things.

The pixel format creation wgl functions ARE extensible unlike ChoosePixelFormat and family. Think of the initial context creation as a 'safe mode' where you have a valid context that can generate handles to functions you need to load actual extensions, which allows you to rely temporary wgl* functions to create a pixel format that is optimized for the device. Take a look at what you can do with wglChoosePixelFormatARB that you can't with ChoosePixelFormat. Each pixel format has its own valid pointers to their own wgl extensions too.

You may also want to implement threading in the future, and maybe even implement framebuffers in said threads. Each thread requires its own context, and you will want to be able to spawn a context that doesn't render directly to the default framebuffer but is also double buffered like everything else. There are definitely valid usecases of having windows give you a window which fully supports what you need. This is why glfw window hinting is a thing.

https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context_(WGL)

@NTimmons
Copy link

NTimmons commented Apr 18, 2022

Ok. This is a very thoughtful and insightful reply with lots of information which will be very useful to those looking to build a robust, safe and well managed large project.

For my use-case: a simple introduction to setting up the very basics of OpenGL without libraries which aren't provided with the Windows SDK/Visual Studio tools. This would complicate the code and reduce the accessibility. Given the information you have stated I am happy with the implementation as-is, as I will not be needing any extensibility, complex programming models or high-levels of specificity - it does not appear to be invalid/improper for this use-case.

For more complex solutions, I am sure developers can make use of the well-developed and maintained GLEW/GLFW for OpenGL extensions and Windows creation. Or more likely, if they need a high-level of control of the rendering interface, they can use Vulkan/DX11/DX12 and their supporting infrastructure and not worry too much about some of the arcane setup for Windows with OpenGL.

@xatja
Copy link

xatja commented May 24, 2022

I'm just thinking, shouldn't the dummy window class be unregistered at around line 117?

@tilkinsc
Copy link

The registration is very lightweight and gets cleaned up by the operating system. You can if you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment