Last active
July 13, 2024 20:06
-
-
Save tilkinsc/7f383faccf3722622f5d0cc9bd45e7e6 to your computer and use it in GitHub Desktop.
WGL Full Demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* MIT License | |
* | |
* Copyright (c) 2017-2022 Cody Tilkins | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
* | |
**/ | |
// please preview this document with tabs, 4 spaces per tab, and no wrapping | |
// https://docs.microsoft.com/en-us/windows/win32/opengl/opengl-on-windows-nt--windows-2000--and-windows-95-98 | |
/* | |
This is a well annotated document for learning how to use wgl. | |
This document contains the following: | |
- Create a window | |
- Create a context | |
- Render & logic on separate thread | |
- Enabling vsync | |
- Ability to show FPS + target an FPS | |
Build with: | |
g++ -std=c++20 -O2 -g0 -o test.exe main.cpp -lstdc++ -lwinmm -lglew32 -lopengl32 -lgdi32 | |
Note: | |
If you want serious error checking, labor looking up all the wgl functions | |
and on error utilize ErrorHandler function. | |
*/ | |
// System Includes | |
#define WIN32_LEAN_AND_MEAN | |
#include <windows.h> | |
#include <stdio.h> | |
#include <mmsystem.h> // timeBeginPeriod timeEndPeriod (or remove this + WIN32_LEAD_AND_MEAN) | |
#include <strsafe.h> // StringCchPrintf | |
#include <thread> | |
#include <chrono> | |
// Local Includes (sub for <> if installed to system, I dont do this) | |
#include "GL/glew.h" | |
#include "GL/wglew.h" | |
// forward declarations | |
static inline void gl_default_error_callback( | |
GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, | |
const GLchar* message, const void* userParam | |
); | |
DWORD WINAPI OpenGLThread(LPVOID lpParam); | |
void ErrorHandler(LPCTSTR lpszFunction); | |
// shared state | |
volatile bool window_active = true; | |
// config | |
volatile bool enable_vsync = false; | |
// data structures | |
typedef struct Context { | |
HWND hWnd; | |
HGLRC gl_context; | |
HDC device_context; | |
} Context; | |
class FPSCount { | |
private: | |
UINT min_ms_wait; | |
public: | |
std::chrono::high_resolution_clock::time_point fps_begin; | |
std::chrono::high_resolution_clock::time_point frame_start; | |
std::chrono::high_resolution_clock::time_point last_start; | |
std::chrono::duration<double> delta_time; | |
uint64_t frames; | |
uint64_t target_fps; | |
FPSCount() | |
{ | |
frames = 0; | |
target_fps = 0; | |
frame_start = std::chrono::high_resolution_clock::now(); | |
last_start = frame_start; | |
fps_begin = frame_start; | |
TIMECAPS capabilities; | |
if (timeGetDevCaps(&capabilities, sizeof(TIMECAPS)) != MMSYSERR_NOERROR) { | |
fprintf(stderr, "Failed to query minimum ms wait time for timer!\n"); | |
exit(1); | |
} | |
min_ms_wait = capabilities.wPeriodMin; | |
} | |
// basic framerate calculation taking the delta between two frames | |
uint64_t updateFPS() { | |
using namespace std::chrono; | |
if (timeBeginPeriod(min_ms_wait) == TIMERR_NOCANDO) { | |
fprintf(stderr, "Failed to begin timer period!\n"); | |
exit(1); | |
} | |
last_start = frame_start; | |
frame_start = high_resolution_clock::now(); | |
delta_time = frame_start - last_start; | |
if(duration<double, std::milli>(frame_start - fps_begin) > 1s) { | |
fps_begin = frame_start; | |
uint64_t temp_frames = frames; | |
frames = 1; | |
return temp_frames; | |
} | |
frames++; | |
if (timeEndPeriod(min_ms_wait) == TIMERR_NOCANDO) { | |
fprintf(stderr, "Failed to end timer period!\n"); | |
exit(1); | |
} | |
return 0; | |
} | |
// sleep for the target fps | |
// does not account for frames that go under target fps | |
// does not account for frames that go over target fps | |
void sleepFrame() { | |
using namespace std::chrono; | |
// wrap time related functions in timeBeginPeriod and timeEndPeriod | |
// always make sure that begin is closed by an end | |
// else you will never get accurate times. Ever. | |
if (timeBeginPeriod(min_ms_wait) == TIMERR_NOCANDO) { | |
fprintf(stderr, "Failed to begin timer period!\n"); | |
exit(1); | |
} | |
// Do not use: std::this_thread::sleep_for(1000ms / target_fps); (good for 40 hz) | |
// This one is good up until 144 hz (186 is where fps shakes violently) | |
// Seek: https://learn.microsoft.com/en-us/windows/win32/multimedia/multimedia-timers | |
Sleep((1000ms / target_fps).count()); | |
if (timeEndPeriod(min_ms_wait) == TIMERR_NOCANDO) { | |
fprintf(stderr, "Failed to end timer period!\n"); | |
exit(1); | |
} | |
} | |
// not sure if this works | |
double getDeltaTime() { | |
return delta_time.count(); | |
} | |
}; | |
// WndProc https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc | |
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) | |
{ | |
switch(message) | |
{ | |
case WM_CREATE: | |
return 0; | |
case WM_CLOSE: | |
window_active = false; | |
return 0; | |
case WM_DESTROY: | |
window_active = false; | |
return 0; | |
} | |
// DefWindowProcA https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowproca | |
return DefWindowProc(hWnd, message, wParam, lParam); | |
} | |
LRESULT CALLBACK WndProcTemp(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) | |
{ | |
switch(message) | |
{ | |
case WM_CREATE: | |
return 0; | |
case WM_DESTROY: | |
return 0; | |
} | |
return DefWindowProc(hWnd, message, wParam, lParam); | |
} | |
// WinMain https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winmain | |
// See Also: https://docs.microsoft.com/en-us/windows/win32/learnwin32/winmain--the-application-entry-point | |
int WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nShowCmd) | |
{ | |
// If you've never encountered this, this makes namespace std local to scope so you | |
// don't have to std:: all over the place and you'd only use it by acknowledging | |
// collisions will be possible | |
using namespace std; | |
///////////////// Section 1: Creating a temporary window | |
// WNDCLASSEX https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexw | |
WNDCLASSEX wc2; | |
wc2.cbSize = sizeof(WNDCLASSEX); | |
wc2.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS; | |
wc2.lpfnWndProc = &WndProcTemp; | |
wc2.cbClsExtra = 0; | |
wc2.cbWndExtra = 0; | |
wc2.hInstance = hInst; | |
wc2.hIcon = 0; | |
wc2.hCursor = LoadCursor(0, IDC_ARROW); | |
wc2.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); | |
wc2.lpszMenuName = 0; | |
wc2.lpszClassName = TEXT("oglversionchecksample_class1"); | |
wc2.hIconSm = 0; | |
// RegisterClassExA https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclassexa | |
// Note: RegisterClassEx is defined to RegisterClassExA | |
if (RegisterClassEx(&wc2) == 0) { | |
fprintf(stderr, "Windows failed to register class `%sl`!\n", wc2.lpszClassName); | |
exit(1); | |
} | |
// CreateWindowEx https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa | |
HWND temp_hWnd = CreateWindowEx( | |
0, // dwExStyle | |
TEXT("oglversionchecksample_class1"), // lpClassName | |
TEXT("oglversionchecksample"), // lpWindowName | |
WS_OVERLAPPEDWINDOW, // dwStyle | |
CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, // X, Y, nWidth, nHeight | |
0, 0, hInst, 0); // hWndParent, hMenu, hInstance, lpParam | |
if (temp_hWnd == nullptr) { | |
fprintf(stderr, "Windows failed to create window `%sl`!\n", wc2.lpszClassName); | |
exit(1); | |
} | |
// GetDC https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc | |
// About device contexs: https://docs.microsoft.com/en-us/windows/win32/gdi/about-device-contexts | |
HDC temp_dc = GetDC(temp_hWnd); | |
if (temp_dc == nullptr) { | |
fprintf(stderr, "Windows failed to provide a display context!\n"); | |
exit(1); | |
} | |
// Please note, a valid correct PFD here will make the loading process faster by like 200-500ms | |
// Which is why I put it verbosely here | |
// PIXELFORMATDESCRIPTOR https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-pixelformatdescriptor | |
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 = 0; | |
pfd.cRedShift = 0; | |
pfd.cGreenBits = 0; | |
pfd.cGreenShift = 0; | |
pfd.cBlueBits = 0; | |
pfd.cBlueShift = 0; | |
pfd.cAlphaBits = 0; | |
pfd.cAlphaShift = 0; | |
pfd.cAccumBits = 0; | |
pfd.cAccumRedBits = 0; | |
pfd.cAccumGreenBits = 0; | |
pfd.cAccumBlueBits = 0; | |
pfd.cAccumAlphaBits = 0; | |
pfd.cDepthBits = 24; | |
pfd.cStencilBits = 8; | |
pfd.cAuxBuffers = 0; | |
pfd.iLayerType = PFD_MAIN_PLANE; | |
pfd.bReserved = 0; | |
pfd.dwLayerMask = 0; | |
pfd.dwVisibleMask = 0; | |
pfd.dwDamageMask = 0; | |
// ChoosePixelFormat https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-choosepixelformat | |
// SetPixelFormat https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setpixelformat | |
int pixelfmt = ChoosePixelFormat(temp_dc, &pfd); | |
if (pixelfmt == 0) { | |
fprintf(stderr, "Failed to choose pixel format!\n"); | |
exit(1); | |
} | |
if (!SetPixelFormat(temp_dc, pixelfmt, &pfd)) { | |
fprintf(stderr, "Could not set pixel format!\n"); | |
exit(1); | |
} | |
printf("Pixel format: %d\n", pixelfmt); | |
///////////////// Section 2: Creating a temporary context | |
// wglCreateContext https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-wglcreatecontext | |
// wglMakeCurrent https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-wglmakecurrent | |
HGLRC temp_gl_context = wglCreateContext(temp_dc); | |
if (temp_gl_context == nullptr) { | |
fprintf(stderr, "Failed to create GL context!\n"); | |
exit(1); | |
} | |
if (!wglMakeCurrent(temp_dc, temp_gl_context)) { | |
fprintf(stderr, "Failed to make GL context current!\n"); | |
exit(1); | |
} | |
PROC proc = wglGetProcAddress("wglGetExtensionsStringEXT"); | |
const char* (*_wglGetExtensionsStringEXT)(void) = (const char* (*)(void)) proc; | |
printf("WGL Extensions Available:\n%s\n\n", _wglGetExtensionsStringEXT()); | |
// Print out basic information about opengl. Here we only care about glewInit | |
// glewInit http://glew.sourceforge.net/basic.html | |
// glGetString: http://docs.gl/gl4/glGetString | |
if (glewInit() != GLEW_OK) { | |
fprintf(stderr, "Failed to initialize glew!\n"); | |
exit(1); | |
} | |
printf( | |
"*temporary*\nGL_RENDERER: %s\nGL_VENDOR: %s\nGL_VERSION: %s\nGL_SHADING_LANGUAGE_VERSION: %s\n\n", | |
glGetString(GL_RENDERER), | |
glGetString(GL_VENDOR), | |
glGetString(GL_VERSION), | |
glGetString(GL_SHADING_LANGUAGE_VERSION) | |
); | |
///////////////// Section 3: Creating a permanent window | |
WNDCLASSEX wc; | |
wc.cbSize = sizeof(WNDCLASSEX); | |
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS; | |
wc.lpfnWndProc = &WndProc; | |
wc.cbClsExtra = 0; | |
wc.cbWndExtra = 0; | |
wc.hInstance = hInst; | |
wc.hIcon = 0; | |
wc.hCursor = LoadCursor(0, IDC_ARROW); | |
wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); | |
wc.lpszMenuName = 0; | |
wc.lpszClassName = TEXT("oglversionchecksample_class"); | |
wc.hIconSm = 0; | |
if (RegisterClassEx(&wc) == 0) { | |
fprintf(stderr, "Windows failed to register class `%sl`!\n", wc.lpszClassName); | |
exit(1); | |
} | |
HWND hWnd = CreateWindowEx( | |
0, // dwExStyle | |
TEXT("oglversionchecksample_class"), // lpClassName | |
TEXT("oglversionchecksample"), // lpWindowName | |
WS_OVERLAPPEDWINDOW, // dwStyle | |
CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, // X, Y, nWidth, nHeight | |
0, 0, hInst, 0); // hWndParent, hMenu, hInstance, lpParam | |
ShowWindow(hWnd, SW_SHOW); | |
if (hWnd == nullptr) { | |
fprintf(stderr, "Windows failed to create window `%sl`!\n", wc2.lpszClassName); | |
exit(1); | |
} | |
HDC dc = GetDC(hWnd); | |
if (dc == nullptr) { | |
fprintf(stderr, "GetDC() failed!\n"); | |
exit(1); | |
} | |
///////////////// Section 4: Creating a permanent context | |
// wglChoosePixelFormatARB https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt | |
int pixel_format = 0; | |
unsigned int num_pixel_format = 0; | |
static const int pixel_attribs[] = { | |
WGL_SUPPORT_OPENGL_ARB, GL_TRUE, | |
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, | |
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, | |
WGL_DOUBLE_BUFFER_ARB, GL_TRUE, | |
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, | |
WGL_COLOR_BITS_ARB, 24, | |
WGL_DEPTH_BITS_ARB, 24, | |
WGL_STENCIL_BITS_ARB, 8, | |
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE, | |
WGL_SAMPLES_ARB, 4, | |
0 | |
}; | |
if (!wglChoosePixelFormatARB(dc, pixel_attribs, NULL, 1, &pixel_format, &num_pixel_format)) { | |
fprintf(stderr, "Failed to choose pixel format!\n"); | |
exit(1); | |
} | |
PIXELFORMATDESCRIPTOR pfd2; | |
if (DescribePixelFormat(dc, pixel_format, sizeof(pfd2), &pfd2) == 0) { | |
fprintf(stderr, "Failed to describe pixel format!\n"); | |
exit(1); | |
} | |
if (!SetPixelFormat(dc, pixel_format, &pfd2)) { | |
fprintf(stderr, "Unable to set pixel format!\n"); | |
exit(1); | |
} | |
// 4.6 forward compatible core profile | |
const int attribs[] = { | |
WGL_CONTEXT_MAJOR_VERSION_ARB, 4, | |
WGL_CONTEXT_MINOR_VERSION_ARB, 6, | |
WGL_CONTEXT_LAYER_PLANE_ARB, 0, // main plane | |
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB, | |
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, | |
0 | |
}; | |
// wglCreateContextAttribsARB https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt | |
HGLRC gl_context = wglCreateContextAttribsARB(dc, NULL, attribs); | |
if (gl_context == nullptr) { | |
fprintf(stderr, "2 Could not create wgl context!\n"); | |
exit(1); | |
} | |
// wglDeleteContext https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-wgldeletecontext | |
if (!wglDeleteContext(temp_gl_context)) { | |
fprintf(stderr, "Failed to delete context!\n"); | |
exit(1); | |
} | |
// ReleaseDC https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc | |
if (ReleaseDC(temp_hWnd, temp_dc) == 0) { | |
fprintf(stderr, "Tried to release DC but failed!\n"); | |
exit(1); | |
} | |
if (!DestroyWindow(temp_hWnd)) { | |
fprintf(stderr, "Failed to destroy window!\n"); | |
exit(1); | |
} | |
///////////////// Section 6: Executing a render thread + handling messages | |
Context context; | |
context.hWnd = hWnd; | |
context.gl_context = gl_context; | |
context.device_context = dc; | |
// CreateThread https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread | |
// Note: Runs the thread immediately | |
DWORD ogl_thread_id = 0; | |
HANDLE ogl_thread = CreateThread( | |
NULL, // lpThreadAttributes | |
0, // dwStackSize | |
OpenGLThread, // lpStartAddress | |
&context, // lpParameter | |
0, // dwCreationFlags | |
&ogl_thread_id // lpThreadId | |
); | |
if (ogl_thread == NULL) { | |
ErrorHandler(TEXT("CreateThread")); | |
exit(1); | |
} | |
// Keep the pump active, do NOT handle pump on another thread | |
MSG msg = {0}; | |
while (window_active && GetMessage(&msg, hWnd, 0, 0) > 0) | |
{ | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
} | |
// 'Join' the thread | |
// WaitForSingleObject https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject | |
WaitForSingleObject(ogl_thread, 0); | |
// Thread may still be active, you must test | |
// GetExitCodeThread https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodethread | |
DWORD error_code = 0; | |
if (!GetExitCodeThread(ogl_thread, &error_code)) { | |
fprintf(stderr, "Windows failed to get thread's error code!\n"); | |
exit(1); | |
} | |
int wait_chances = 100; // 1500ms before | |
while (error_code == 259 && wait_chances >= 0) { | |
// Note: Timer resolution is 15.625 | |
WaitForSingleObject(ogl_thread, 15); | |
if (!GetExitCodeThread(ogl_thread, &error_code)) { | |
fprintf(stderr, "Windows failed to get thread's error code!\n"); | |
exit(1); | |
} | |
} | |
if (error_code != 0) { | |
if (wait_chances < 0) { | |
fprintf(stderr, "Timed out while joining thread. It's still active. Thread probably hanging.\n"); | |
} else { | |
fprintf(stderr, "There was an error in the thread. %ld\n", error_code); | |
} | |
exit(1); | |
} | |
puts("Exiting..."); | |
// PostQuitMessage https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postquitmessage | |
PostQuitMessage(error_code); | |
return EXIT_SUCCESS; | |
} | |
// OpenGLThread aka ThreadProc https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms686736(v=vs.85) | |
DWORD WINAPI OpenGLThread(LPVOID lpParam) | |
{ | |
// This is set up for random background color via rand() | |
{ | |
using namespace std::chrono; | |
srand(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count()); | |
} | |
Context context = *(Context*) lpParam; | |
if (!wglMakeCurrent(context.device_context, context.gl_context)) { | |
fprintf(stderr, "Failed to make context current in thread!\n"); | |
window_active = false; | |
return 1; | |
} | |
// glDebugMessageCallback http://docs.gl/gl4/glDebugMessageCallback | |
// glDebugMessageControl http://docs.gl/gl4/glDebugMessageControl | |
// https://registry.khronos.org/OpenGL/extensions/KHR/KHR_debug.txt | |
// Note: On NVidia cards the glDebugMessageControl makes it so creating VAOs isn't spammy | |
glEnable(GL_DEBUG_OUTPUT); | |
glDebugMessageCallback((GLDEBUGPROC) &gl_default_error_callback, NULL); | |
//glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, 0, GL_FALSE); | |
// glGetIntegerv: http://docs.gl/gl4/glGet | |
GLint profile_mask = 0; | |
GLint context_flags = 0; | |
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile_mask); | |
glGetIntegerv(GL_CONTEXT_FLAGS, &context_flags); | |
printf( | |
"*final*\nGL_RENDERER: %s\nGL_VENDOR: %s\nGL_VERSION: %s\nGL_SHADING_LANGUAGE_VERSION: %s\n", | |
glGetString(GL_RENDERER), | |
glGetString(GL_VENDOR), | |
glGetString(GL_VERSION), | |
glGetString(GL_SHADING_LANGUAGE_VERSION) | |
); | |
printf( | |
"GL_CONTEXT_CORE_PROFILE: %d\nGL_CONTEXT_COMPATIBILITY_PROFILE: %d\n", | |
profile_mask & GL_CONTEXT_CORE_PROFILE_BIT ? 1 : 0, | |
profile_mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT ? 1 : 0 | |
); | |
printf( | |
"GL_CONTEXT_FLAG_FORWARD_COMPATIBLE: %d\nGL_CONTEXT_FLAG_DEBUG: %d\nGL_CONTEXT_FLAG_ROBUST_ACCESS: %d\nGL_CONTEXT_FLAG_NO_ERROR: %d\n", | |
context_flags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT ? 1 : 0, | |
context_flags & GL_CONTEXT_FLAG_DEBUG_BIT ? 1 : 0, | |
context_flags & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT ? 1 : 0, | |
context_flags & GL_CONTEXT_FLAG_NO_ERROR_BIT ? 1 : 0 | |
); | |
// If you can't use the glDebugMessageCallback have fun using this too much | |
// { | |
// GLenum err = glGetError(); | |
// if (err != GL_NO_ERROR) { | |
// printf("Warning: An opengl error has occured %x\n", err); | |
// } | |
// } | |
// wglSwapIntervalEXT https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt | |
int vsync_enabled = 0; | |
if (enable_vsync) { | |
if (!wglSwapIntervalEXT(1)) { | |
fprintf(stderr, "Warning: Failed to wglSwapIntervalEXT(1). No VSync.\n"); | |
} else { | |
vsync_enabled = 1; | |
} | |
} | |
// glEnable http://docs.gl/gl4/glEnable | |
// glDepthFunc http://docs.gl/gl4/glDepthFunc | |
// glBlendFunc http://docs.gl/gl4/glBlendFunc | |
// glCullFace http://docs.gl/gl4/glCullFace | |
// glPolygonMode http://docs.gl/gl4/glPolygonMode | |
glEnable(GL_DEPTH_TEST); | |
glDepthFunc(GL_LEQUAL); | |
glEnable(GL_BLEND); | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
glEnable(GL_CULL_FACE); | |
glCullFace(GL_BACK); | |
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); | |
// glViewport http://docs.gl/gl4/glViewport | |
glViewport(0, 0, 640, 480); | |
// This sets both framebuffers colors to the same color so the screen doesn't flash an odd color | |
// glClearColor http://docs.gl/gl4/glClearColor | |
// glClear http://docs.gl/gl4/glClear | |
// SwapBuffers https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers | |
// ShowWindow https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow | |
glClearColor(((float) rand() / RAND_MAX), ((float) rand() / RAND_MAX), ((float) rand() / RAND_MAX), 0.0f); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
if (!SwapBuffers(context.device_context)) { | |
fprintf(stderr, "Failed to swap buffers!\n"); | |
window_active = false; | |
return 1; | |
} | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
if (!SwapBuffers(context.device_context)) { | |
fprintf(stderr, "Failed to swap buffers!\n"); | |
window_active = false; | |
return 1; | |
} | |
if (!ShowWindow(context.hWnd, 1)) { | |
fprintf(stderr, "Windows failed to show window!\n"); | |
window_active = false; | |
return 1; | |
} | |
char title_buffer[256]; | |
FPSCount fps; | |
fps.target_fps = 60; | |
while (window_active) | |
{ | |
size_t fps_update = fps.updateFPS(); | |
if(fps_update) { | |
snprintf(title_buffer, 256, "%s [FPS: %zu]\n", "OpenGL Version Check Sample", fps_update); | |
// SetWindowTextA https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowtexta?redirectedfrom=MSDN | |
SetWindowTextA(context.hWnd, title_buffer); | |
} | |
// TODO: Handle Keyboard State | |
// TODO: Handle Game Logic | |
// FLASH WARNING: Only uncomment this line to while listening to https://www.youtube.com/watch?v=SeMXa5lBGYc | |
// glClearColor(((float) rand() / RAND_MAX), ((float) rand() / RAND_MAX), ((float) rand() / RAND_MAX), 0.0f); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
// TODO: Render | |
// vsync is here | |
if (!SwapBuffers(context.device_context)) { | |
fprintf(stderr, "Failed to swap buffers!\n"); | |
window_active = false; | |
return 1; | |
} | |
// FPS Sleep, if no vsync enabled | |
if (!enable_vsync || !vsync_enabled) { | |
fps.sleepFrame(); | |
} | |
} | |
return 0; | |
} | |
// Taken directly from https://docs.microsoft.com/en-us/windows/win32/procthread/creating-threads?redirectedfrom=MSDN | |
// Copyright https://docs.microsoft.com/en-us/legal/termsofuse | |
// This is for educational purposes only and is required to not be included in anything you are merchanting | |
void ErrorHandler(LPCTSTR lpszFunction) | |
{ | |
// Retrieve the system error message for the last-error code. | |
LPVOID lpMsgBuf; | |
LPVOID lpDisplayBuf; | |
DWORD dw = GetLastError(); | |
FormatMessage( | |
FORMAT_MESSAGE_ALLOCATE_BUFFER | | |
FORMAT_MESSAGE_FROM_SYSTEM | | |
FORMAT_MESSAGE_IGNORE_INSERTS, | |
NULL, | |
dw, | |
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
(LPTSTR) &lpMsgBuf, | |
0, NULL ); | |
// Display the error message. | |
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, | |
(lstrlen((LPCTSTR) lpMsgBuf) + lstrlen((LPCTSTR) lpszFunction) + 40) * sizeof(TCHAR)); | |
StringCchPrintf((LPTSTR)lpDisplayBuf, | |
LocalSize(lpDisplayBuf) / sizeof(TCHAR), | |
TEXT("%s failed with error %d: %s"), | |
lpszFunction, dw, lpMsgBuf); | |
MessageBox(NULL, (LPCTSTR) lpDisplayBuf, TEXT("Error"), MB_OK); | |
// Free error-handling buffer allocations. | |
LocalFree(lpMsgBuf); | |
LocalFree(lpDisplayBuf); | |
} | |
// Resolves various enums from opengl error callbacks into something humanly understandable | |
static inline void gl_default_error_callback( | |
GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, | |
const GLchar* message, const void* userParam | |
){ | |
const char* source_s; | |
switch(source) { | |
case GL_DEBUG_SOURCE_API: | |
source_s = "API"; | |
break; | |
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: | |
source_s = "WINDOW_SYSTEM"; | |
break; | |
case GL_DEBUG_SOURCE_SHADER_COMPILER: | |
source_s = "SHADER_COMPILER"; | |
break; | |
case GL_DEBUG_SOURCE_THIRD_PARTY: | |
source_s = "THIRD_PARTY"; | |
break; | |
case GL_DEBUG_SOURCE_APPLICATION: | |
source_s = "APPLICATION"; | |
break; | |
case GL_DEBUG_SOURCE_OTHER: | |
source_s = "OTHER"; | |
break; | |
default: | |
source_s = "UNKNOWN_SOURCE"; | |
break; | |
} | |
const char* type_s; | |
switch(type) { | |
case GL_DEBUG_TYPE_ERROR: | |
type_s = "ERROR"; | |
break; | |
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: | |
type_s = "DEPRECATED_BEHAVIOR"; | |
break; | |
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: | |
type_s = "UNDEFINED_BEHAVIOR"; | |
break; | |
case GL_DEBUG_TYPE_PORTABILITY: | |
type_s = "PORTABILITY"; | |
break; | |
case GL_DEBUG_TYPE_PERFORMANCE: | |
type_s = "PERFORMANCE"; | |
break; | |
case GL_DEBUG_TYPE_MARKER: | |
type_s = "MARKER"; | |
break; | |
case GL_DEBUG_TYPE_PUSH_GROUP: | |
type_s = "PUSH_GROUP"; | |
break; | |
case GL_DEBUG_TYPE_POP_GROUP: | |
type_s = "POP_GROUP"; | |
break; | |
case GL_DEBUG_TYPE_OTHER: | |
type_s = "OTHER"; | |
break; | |
default: | |
type_s = "UNKNOWN_TYPE"; | |
break; | |
} | |
const char* severity_s; | |
switch(severity) { | |
case GL_DEBUG_SEVERITY_HIGH: | |
severity_s = "HIGH"; | |
break; | |
case GL_DEBUG_SEVERITY_MEDIUM: | |
severity_s = "MEDIUM"; | |
break; | |
case GL_DEBUG_SEVERITY_LOW: | |
severity_s = "LOW"; | |
break; | |
case GL_DEBUG_SEVERITY_NOTIFICATION: | |
severity_s = "NOTIFICATION"; | |
break; | |
default: | |
severity_s = "UNKNOWN_SEVERITY"; | |
break; | |
} | |
const char* id_s; | |
switch(id) { | |
case GL_NO_ERROR: | |
id_s = "NO_ERROR"; | |
break; | |
case GL_INVALID_ENUM: | |
id_s = "INVALID_ENUM"; | |
break; | |
case GL_INVALID_VALUE: | |
id_s = "INVALID_VALUE"; | |
break; | |
case GL_INVALID_OPERATION: | |
id_s = "INVALID_OPERATION"; | |
break; | |
case GL_STACK_OVERFLOW: | |
id_s = "STACK_OVERFLOW"; | |
break; | |
case GL_STACK_UNDERFLOW: | |
id_s = "STACK_UNDERFLOW"; | |
break; | |
case GL_OUT_OF_MEMORY: | |
id_s = "OUT_OF_MEMORY"; | |
break; | |
case GL_INVALID_FRAMEBUFFER_OPERATION: | |
id_s = "INVALID_FRAMEBUFFER_OPERATION"; | |
break; | |
case GL_CONTEXT_LOST: | |
id_s = "CONTEXT_LOST"; | |
break; | |
case GL_TABLE_TOO_LARGE: | |
id_s = "TABLE_TOO_LARGE"; | |
break; | |
default: | |
id_s = "UNKNOWN_ID"; | |
break; | |
} | |
fprintf(stderr, "OpenGL message caught:\n\t`[source ] - %s(0x%x)`\n\t`[type ] - %s(0x%x)`\n\t`[id ] - %s(0x%x)`\n\t`[severity] - %s(0x%x)`\n\nGot length %d message:\n`%s`\n\n\n", source_s, source, type_s, type, id_s, id, severity_s, severity, length, message); | |
} | |
This code is a lot cleaner especially with the inclusion of the no error and robust access context flag, which as you probably noticed AMD still doesn't support.
If I had any feedback left, I would say to check out WGL_EXT_swap_control_tear
which is a relaxed V-Sync mode that doesn't cause stuttering issues if the FPS is lower than the refresh rate and frames start to come in late.
I'm not a fan of adaptive vsync
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example output: