// example how to set up modern OpenGL context with fallback to legacy context // set to 0 to create resizable window #define WINDOW_WIDTH 1280 #define WINDOW_HEIGHT 720 // do you need depth buffer? #define WINDOW_DEPTH 1 // do you need stencil buffer? #define WINDOW_STENCIL 0 // use sRGB for color buffer #define WINDOW_SRGB 1 // do you need multisampling? // to disable set to 0, to enable set to 2, 4, 8, 16, ... #define WINDOW_MSAA 4 // do you need vsync? #define WINDOW_VSYNC 1 // keep this enabled when debugging #define USE_DEBUG_MODE 1 // replace this with your favorite assert() implementation #include #define WIN32_LEAN_AND_MEAN #include #include #include "glext.h" // download from https://www.opengl.org/registry/api/GL/glext.h #include "wglext.h" // download from https://www.opengl.org/registry/api/GL/wglext.h // https://www.opengl.org/registry/specs/ARB/wgl_extensions_string.txt // https://www.opengl.org/registry/specs/ARB/wgl_pixel_format.txt // https://www.opengl.org/registry/specs/ARB/wgl_create_context.txt // https://www.opengl.org/registry/specs/EXT/wgl_swap_control.txt // https://www.opengl.org/registry/specs/EXT/wgl_swap_control_tear.txt // https://www.opengl.org/registry/specs/ARB/framebuffer_sRGB.txt // https://www.opengl.org/registry/specs/ARB/multisample.txt // https://www.opengl.org/registry/specs/ARB/debug_output.txt #pragma comment (lib, "gdi32.lib") #pragma comment (lib, "user32.lib") #pragma comment (lib, "opengl32.lib") #if USE_DEBUG_MODE #define GL_CHECK(x) do \ { \ x; \ GLenum err = glGetError(); \ assert(err == GL_NO_ERROR); \ } while (0) #else #define GL_CHECK(x) x #endif // called after context is set up (only once) // for example, load GL extensions here & set up GL state static void RenderInit(void) { GL_CHECK( glClearColor(100.f/255.f, 149.f/255.f, 237.f/255.f, 1.f) ); } // called before render is destroyed static void RenderDone(void) { } // called when window is resized static void RenderResize(unsigned width, unsigned height) { GL_CHECK( glViewport(0, 0, width, height) ); } // called every frame before swapping buffers static void RenderFrame(void) { GL_CHECK( glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) ); // use legacy GL for drawing to display a triangle glBegin(GL_TRIANGLES); glColor3f(1.f, 0.f, 0.f); glVertex2f(0.f, 0.5f); glColor3f(0.f, 1.f, 0.f); glVertex2f(0.5f, -0.5f); glColor3f(0.f, 0.f, 1.f); glVertex2f(-0.5f, -0.5f); GL_CHECK( glEnd() ); } static void LogWin32Error(const char* msg) { OutputDebugStringA(msg); OutputDebugStringA("!\n"); DWORD err = GetLastError(); LPWSTR str; if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), (LPWSTR)&str, 0, NULL)) { OutputDebugStringW(str); LocalFree(str); } } static HGLRC CreateOldOpenGLContext(HDC dc) { PIXELFORMATDESCRIPTOR pfd = { .nSize = sizeof(pfd), .nVersion = 1, .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | (WINDOW_DEPTH ? 0 : PFD_DEPTH_DONTCARE), .iPixelType = PFD_TYPE_RGBA, .cColorBits = 24, .cDepthBits = (WINDOW_DEPTH ? 24 : 0), .cStencilBits = (WINDOW_STENCIL ? 8 : 0), }; int format = ChoosePixelFormat(dc, &pfd); if (!format) { LogWin32Error("ChoosePixelFormat failed"); return NULL; } if (!DescribePixelFormat(dc, format, sizeof(pfd), &pfd)) { LogWin32Error("DescribePixelFormat failed"); return NULL; } if (!SetPixelFormat(dc, format, &pfd)) { LogWin32Error("SetPixelFormat failed"); return NULL; } HGLRC rc = wglCreateContext(dc); if (!rc) { LogWin32Error("wglCreateContext failed"); return NULL; } if (!wglMakeCurrent(dc, rc)) { LogWin32Error("wglMakeCurrent failed"); wglDeleteContext(rc); return NULL; } return rc; } #if USE_DEBUG_MODE static void APIENTRY OpenGLDebugCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void* user) { OutputDebugStringA(message); OutputDebugStringA("\n"); if (severity >= GL_DEBUG_SEVERITY_LOW_ARB && severity <= GL_DEBUG_SEVERITY_HIGH_ARB) { assert(0); } } #endif static int StringsAreEqual(const char* src, const char* dst, size_t dstlen) { while (*src && dstlen-- && *dst) { if (*src++ != *dst++) { return 0; } } return (dstlen && *src == *dst) || (!dstlen && *src == 0); } static HGLRC CreateOpenGLContext(HDC dc) { PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; int wgl_ARB_multisample = 0; int wgl_ARB_framebuffer_sRGB = 0; int wgl_EXT_swap_control_tear = 0; HWND wnd = CreateWindowExW(0, L"STATIC", L"Dummy Window", WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL); if (wnd) { HDC dc = GetDC(wnd); if (dc) { HGLRC rc = CreateOldOpenGLContext(dc); if (rc) { PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = (void*)wglGetProcAddress("wglGetExtensionsStringARB"); if (wglGetExtensionsStringARB != (void*)0 && wglGetExtensionsStringARB != (void*)1 && wglGetExtensionsStringARB != (void*)2 && wglGetExtensionsStringARB != (void*)3 && wglGetExtensionsStringARB != (void*)-1) { const char* ext = wglGetExtensionsStringARB(dc); if (ext) { const char* start = ext; for (;;) { while (*ext != 0 && *ext != ' ') { ext++; } size_t length = ext - start; if (StringsAreEqual("WGL_ARB_pixel_format", start, length)) { wglChoosePixelFormatARB = (void*)wglGetProcAddress("wglChoosePixelFormatARB"); } else if (StringsAreEqual("WGL_ARB_create_context", start, length)) { wglCreateContextAttribsARB = (void*)wglGetProcAddress("wglCreateContextAttribsARB"); } else if (StringsAreEqual("WGL_EXT_swap_control", start, length)) { wglSwapIntervalEXT = (void*)wglGetProcAddress("wglSwapIntervalEXT"); } else if (StringsAreEqual("WGL_ARB_framebuffer_sRGB", start, length)) { wgl_ARB_framebuffer_sRGB = 1; } else if (StringsAreEqual("WGL_ARB_multisample", start, length)) { wgl_ARB_multisample = 1; } else if (StringsAreEqual("WGL_ARB_framebuffer_sRGB", start, length)) { wgl_ARB_framebuffer_sRGB = 1; } else if (StringsAreEqual("WGL_EXT_swap_control_tear", start, length)) { wgl_EXT_swap_control_tear = 1; } if (*ext == 0) { break; } ext++; start = ext; } } } wglMakeCurrent(NULL, NULL); wglDeleteContext(rc); } ReleaseDC(wnd, dc); } DestroyWindow(wnd); } HGLRC rc = NULL; if (wglCreateContextAttribsARB && wglChoosePixelFormatARB) { int attrib[32]; int* p = attrib; *p++ = WGL_DRAW_TO_WINDOW_ARB; *p++ = GL_TRUE; *p++ = WGL_ACCELERATION_ARB; *p++ = WGL_FULL_ACCELERATION_ARB; *p++ = WGL_SUPPORT_OPENGL_ARB; *p++ = GL_TRUE; *p++ = WGL_DOUBLE_BUFFER_ARB; *p++ = GL_TRUE; *p++ = WGL_PIXEL_TYPE_ARB; *p++ = WGL_TYPE_RGBA_ARB; *p++ = WGL_COLOR_BITS_ARB; *p++ = 24; if (WINDOW_DEPTH) { *p++ = WGL_DEPTH_BITS_ARB; *p++ = 24; } if (WINDOW_STENCIL) { *p++ = WGL_STENCIL_BITS_ARB; *p++ = 8; } if (WINDOW_SRGB && wgl_ARB_framebuffer_sRGB) { *p++ = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB; *p++ = GL_TRUE; } if (WINDOW_MSAA && wgl_ARB_multisample) { *p++ = WGL_SAMPLE_BUFFERS_ARB; *p++ = 1; *p++ = WGL_SAMPLES_ARB; *p++ = WINDOW_MSAA; } *p = 0; int format; UINT formats; if (!wglChoosePixelFormatARB(dc, attrib, NULL, 1, &format, &formats) || formats == 0) { LogWin32Error("wglChoosePixelFormatARB failed"); } else { PIXELFORMATDESCRIPTOR pfd = { .nSize = sizeof(pfd), }; if (!DescribePixelFormat(dc, format, sizeof(pfd), &pfd)) { LogWin32Error("DescribePixelFormat failed"); return NULL; } else { if (!SetPixelFormat(dc, format, &pfd)) { LogWin32Error("SetPixelFormat failed"); } else { int ctx[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 0, #if USE_DEBUG_MODE WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, #endif WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, 0, }; rc = wglCreateContextAttribsARB(dc, NULL, ctx); if (!rc) { LogWin32Error("wglCreateContextAttribsARB failed"); } else { if (!wglMakeCurrent(dc, rc)) { LogWin32Error("wglMakeCurrent failed"); wglDeleteContext(rc); rc = NULL; } else { if (WINDOW_MSAA && wgl_ARB_multisample) { GL_CHECK(glEnable(GL_MULTISAMPLE_ARB)); } } } } } } } if (!rc) { OutputDebugStringA("Failed to create modern OpenGL context, retrying with legacy context!\n"); rc = CreateOldOpenGLContext(dc); } if (rc) { if (WINDOW_VSYNC && wglSwapIntervalEXT) { if (!wglSwapIntervalEXT(wgl_EXT_swap_control_tear ? -1 : 1)) { LogWin32Error("wglSwapIntervalEXT failed"); } } #if USE_DEBUG_MODE const GLubyte* ext; GL_CHECK( ext = glGetString(GL_EXTENSIONS) ); if (ext) { const GLubyte* start = ext; for (;;) { while (*ext != 0 && *ext != ' ') { ext++; } size_t length = ext - start; if (StringsAreEqual("GL_ARB_debug_output", (const char*)start, length)) { PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB = (void*)wglGetProcAddress("glDebugMessageCallbackARB"); GL_CHECK( glDebugMessageCallbackARB(OpenGLDebugCallback, NULL) ); GL_CHECK( glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB) ); break; } if (*ext == 0) { break; } ext++; start = ext; } } #endif } return rc; } struct Win32Context { HDC dc; HGLRC rc; }; static LRESULT CALLBACK WindowProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) { struct Win32Context* ctx = (void*)GetWindowLongPtr(wnd, GWLP_USERDATA); switch (msg) { case WM_CREATE: ctx = ((CREATESTRUCTW*)lparam)->lpCreateParams; SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)ctx); if (!(ctx->dc = GetDC(wnd))) { LogWin32Error("GetDC failed"); return -1; } if (!(ctx->rc = CreateOpenGLContext(ctx->dc))) { ReleaseDC(wnd, ctx->dc); return -1; } RenderInit(); return 0; case WM_DESTROY: if (ctx->rc) { RenderDone(); wglMakeCurrent(NULL, NULL); wglDeleteContext(ctx->rc); } if (ctx->dc) { ReleaseDC(wnd, ctx->dc); } PostQuitMessage(0); return 0; case WM_SIZE: RenderResize(LOWORD(lparam), HIWORD(lparam)); return 0; } return DefWindowProcW(wnd, msg, wparam, lparam); } int WINAPI WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR cmd_line, int cmd_show) { WNDCLASSEXW wc = { .cbSize = sizeof(wc), .lpfnWndProc = WindowProc, .hInstance = instance, .hIcon = LoadIconA(NULL, IDI_APPLICATION), .hCursor = LoadCursorA(NULL, IDC_ARROW), .lpszClassName = L"opengl_window_class", }; if (!RegisterClassExW(&wc)) { LogWin32Error("RegisterClassEx failed"); } else { int width = CW_USEDEFAULT; int height = CW_USEDEFAULT; DWORD exstyle = WS_EX_APPWINDOW; DWORD style = WS_OVERLAPPEDWINDOW; if (WINDOW_WIDTH && WINDOW_HEIGHT) { style &= ~WS_THICKFRAME & ~WS_MAXIMIZEBOX; RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT }; if (!AdjustWindowRectEx(&rect, style, FALSE, exstyle)) { LogWin32Error("AdjustWindowRectEx failed"); style = WS_OVERLAPPEDWINDOW; } else { width = rect.right - rect.left; height = rect.bottom - rect.top; } } struct Win32Context ctx; HWND wnd = CreateWindowExW(exstyle, wc.lpszClassName, L"OpenGL Window", style | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, wc.hInstance, &ctx); if (!wnd) { LogWin32Error("CreateWindow failed"); } else { for (;;) { MSG msg; if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { break; } TranslateMessage(&msg); DispatchMessageW(&msg); continue; } RenderFrame(); if (!SwapBuffers(ctx.dc)) { LogWin32Error("SwapBuffers failed"); } } } UnregisterClassW(wc.lpszClassName, wc.hInstance); } return 0; }