Skip to content

Instantly share code, notes, and snippets.

@tilkinsc
Last active July 13, 2024 20:06
Show Gist options
  • Save tilkinsc/7f383faccf3722622f5d0cc9bd45e7e6 to your computer and use it in GitHub Desktop.
Save tilkinsc/7f383faccf3722622f5d0cc9bd45e7e6 to your computer and use it in GitHub Desktop.
WGL Full Demo
/**
*
* 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);
}
@acceleration3
Copy link

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.

@tilkinsc
Copy link
Author

tilkinsc commented Oct 7, 2022

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