Skip to content

Instantly share code, notes, and snippets.

@acceleration3
Created October 7, 2022 20:06
Show Gist options
  • Save acceleration3/72d7db66c7b93adb8030bf6598eda67f to your computer and use it in GitHub Desktop.
Save acceleration3/72d7db66c7b93adb8030bf6598eda67f to your computer and use it in GitHub Desktop.
C++11 - WGL bootstrapping from scratch
/*
MIT License
Copyright 2022 acceleration3
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.
*/
/*
This is an example of how to setup WGL in Windows using C++ in a C-like way, as that lends itself more to demonstration purposes.
That being said, some of the things here could and should be encapsulated (window class, opengl class, context class, renderer class, etc...).
*/
#include <string>
#include <iostream>
#include <memory>
#include <vector>
#include <thread>
#include <atomic>
#include <windows.h>
#include <gl/GL.h>
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// Global constants
// ----------------------------------------------------------------------------------------------------------------------------------------------------
static const std::basic_string<TCHAR> g_class_name = TEXT("MainWindowClass");
static const std::basic_string<TCHAR> g_window_title = TEXT("WGL Bootstrap");
static const std::basic_string<TCHAR> g_dummy_class_name = TEXT("DummyClass");
static const int g_window_width = 1280;
static const int g_window_height = 720;
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// Details
// ----------------------------------------------------------------------------------------------------------------------------------------------------
/*
Normally I would use an OpenGL extension loader library to load the GL/WGL extensions but to make this a proper from-scratch guide I will do it manually.
I recommend GLAD 2 (https://gen.glad.sh/) in particular.
*/
namespace details
{
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// Definitions
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// WGL
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
#define WGL_DRAW_TO_BITMAP_ARB 0x2002
#define WGL_ACCELERATION_ARB 0x2003
#define WGL_NEED_PALETTE_ARB 0x2004
#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005
#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006
#define WGL_SWAP_METHOD_ARB 0x2007
#define WGL_NUMBER_OVERLAYS_ARB 0x2008
#define WGL_NUMBER_UNDERLAYS_ARB 0x2009
#define WGL_TRANSPARENT_ARB 0x200A
#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037
#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038
#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039
#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A
#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B
#define WGL_SHARE_DEPTH_ARB 0x200C
#define WGL_SHARE_STENCIL_ARB 0x200D
#define WGL_SHARE_ACCUM_ARB 0x200E
#define WGL_SUPPORT_GDI_ARB 0x200F
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#define WGL_STEREO_ARB 0x2012
#define WGL_PIXEL_TYPE_ARB 0x2013
#define WGL_COLOR_BITS_ARB 0x2014
#define WGL_RED_BITS_ARB 0x2015
#define WGL_RED_SHIFT_ARB 0x2016
#define WGL_GREEN_BITS_ARB 0x2017
#define WGL_GREEN_SHIFT_ARB 0x2018
#define WGL_BLUE_BITS_ARB 0x2019
#define WGL_BLUE_SHIFT_ARB 0x201A
#define WGL_ALPHA_BITS_ARB 0x201B
#define WGL_ALPHA_SHIFT_ARB 0x201C
#define WGL_ACCUM_BITS_ARB 0x201D
#define WGL_ACCUM_RED_BITS_ARB 0x201E
#define WGL_ACCUM_GREEN_BITS_ARB 0x201F
#define WGL_ACCUM_BLUE_BITS_ARB 0x2020
#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021
#define WGL_DEPTH_BITS_ARB 0x2022
#define WGL_STENCIL_BITS_ARB 0x2023
#define WGL_AUX_BUFFERS_ARB 0x2024
#define WGL_NO_ACCELERATION_ARB 0x2025
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
#define WGL_FULL_ACCELERATION_ARB 0x2027
#define WGL_SWAP_EXCHANGE_ARB 0x2028
#define WGL_SWAP_COPY_ARB 0x2029
#define WGL_SWAP_UNDEFINED_ARB 0x202A
#define WGL_TYPE_RGBA_ARB 0x202B
#define WGL_TYPE_COLORINDEX_ARB 0x202C
#define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000
#define WGL_DRAW_TO_WINDOW_EXT 0x2001
#define WGL_DRAW_TO_BITMAP_EXT 0x2002
#define WGL_ACCELERATION_EXT 0x2003
#define WGL_NEED_PALETTE_EXT 0x2004
#define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005
#define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006
#define WGL_SWAP_METHOD_EXT 0x2007
#define WGL_NUMBER_OVERLAYS_EXT 0x2008
#define WGL_NUMBER_UNDERLAYS_EXT 0x2009
#define WGL_TRANSPARENT_EXT 0x200A
#define WGL_TRANSPARENT_VALUE_EXT 0x200B
#define WGL_SHARE_DEPTH_EXT 0x200C
#define WGL_SHARE_STENCIL_EXT 0x200D
#define WGL_SHARE_ACCUM_EXT 0x200E
#define WGL_SUPPORT_GDI_EXT 0x200F
#define WGL_SUPPORT_OPENGL_EXT 0x2010
#define WGL_DOUBLE_BUFFER_EXT 0x2011
#define WGL_STEREO_EXT 0x2012
#define WGL_PIXEL_TYPE_EXT 0x2013
#define WGL_COLOR_BITS_EXT 0x2014
#define WGL_RED_BITS_EXT 0x2015
#define WGL_RED_SHIFT_EXT 0x2016
#define WGL_GREEN_BITS_EXT 0x2017
#define WGL_GREEN_SHIFT_EXT 0x2018
#define WGL_BLUE_BITS_EXT 0x2019
#define WGL_BLUE_SHIFT_EXT 0x201A
#define WGL_ALPHA_BITS_EXT 0x201B
#define WGL_ALPHA_SHIFT_EXT 0x201C
#define WGL_ACCUM_BITS_EXT 0x201D
#define WGL_ACCUM_RED_BITS_EXT 0x201E
#define WGL_ACCUM_GREEN_BITS_EXT 0x201F
#define WGL_ACCUM_BLUE_BITS_EXT 0x2020
#define WGL_ACCUM_ALPHA_BITS_EXT 0x2021
#define WGL_DEPTH_BITS_EXT 0x2022
#define WGL_STENCIL_BITS_EXT 0x2023
#define WGL_AUX_BUFFERS_EXT 0x2024
#define WGL_NO_ACCELERATION_EXT 0x2025
#define WGL_GENERIC_ACCELERATION_EXT 0x2026
#define WGL_FULL_ACCELERATION_EXT 0x2027
#define WGL_SWAP_EXCHANGE_EXT 0x2028
#define WGL_SWAP_COPY_EXT 0x2029
#define WGL_SWAP_UNDEFINED_EXT 0x202A
#define WGL_TYPE_RGBA_EXT 0x202B
#define WGL_TYPE_COLORINDEX_EXT 0x202C
#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
#define WGL_CONTEXT_FLAGS_ARB 0x2094
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004
#define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252
#define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256
#define WGL_NO_RESET_NOTIFICATION_ARB 0x8261
#define WGL_SAMPLE_BUFFERS_EXT 0x2041
#define WGL_SAMPLES_EXT 0x2042
#define WGL_SAMPLE_BUFFERS_ARB 0x2041
#define WGL_SAMPLES_ARB 0x2042
#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9
#define WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20A9
#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3
// OpenGL
#define GL_NUM_EXTENSIONS 0x821D
#define GL_SHADING_LANGUAGE_VERSION 0x8B8C
#define GL_FRAMEBUFFER_SRGB 0x8DB9
#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9
#define GL_CONTEXT_FLAGS 0x821E
#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002
#define GL_DEBUG_OUTPUT 0x92E0
#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242
#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243
#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244
#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245
#define GL_DEBUG_SOURCE_API_ARB 0x8246
#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247
#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248
#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249
#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A
#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B
#define GL_DEBUG_TYPE_ERROR_ARB 0x824C
#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D
#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E
#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F
#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250
#define GL_DEBUG_TYPE_OTHER_ARB 0x8251
#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143
#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144
#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145
#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146
#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147
#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// Function pointers
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// WGL
using PFNWGLGETEXTENSIONSSTRINGEXTPROC = const char* (__stdcall*) (void);
using PFNWGLGETEXTENSIONSSTRINGARBPROC = const char* (__stdcall*) (HDC hdc);
using PFNWGLCHOOSEPIXELFORMATEXTPROC = BOOL (__stdcall*) (HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats);
using PFNWGLCHOOSEPIXELFORMATARBPROC = BOOL (__stdcall*) (HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats);
using PFNWGLCREATECONTEXTATTRIBSARBPROC = HGLRC (__stdcall*) (HDC hDC, HGLRC hShareContext, const int* attribList);
using PFNWGLSWAPINTERVALEXTPROC = BOOL (__stdcall*) (int interval);
PFNWGLGETEXTENSIONSSTRINGEXTPROC wglGetExtensionsStringEXT = nullptr;
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = nullptr;
PFNWGLCHOOSEPIXELFORMATEXTPROC wglChoosePixelFormatEXT = nullptr;
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = nullptr;
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = nullptr;
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr;
#define wglGetExtensionsStringEXT details::wglGetExtensionsStringEXT
#define wglGetExtensionsStringARB details::wglGetExtensionsStringARB
#define wglChoosePixelFormatEXT details::wglChoosePixelFormatEXT
#define wglChoosePixelFormatARB details::wglChoosePixelFormatARB
#define wglCreateContextAttribsARB details::wglCreateContextAttribsARB
#define wglSwapIntervalEXT details::wglSwapIntervalEXT
// OpenGL
#define GLchar char
using PFNGLGETSTRINGIPROC = const GLubyte* (__stdcall*) (GLenum name, GLuint index);
using GLDEBUGPROCARB = void (__stdcall*) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam);
using PFNGLDEBUGMESSAGECONTROLARBPROC = void (__stdcall*) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled);
using PFNGLDEBUGMESSAGECALLBACKARBPROC = void (__stdcall*) (GLDEBUGPROCARB callback, const void* userParam);
PFNGLGETSTRINGIPROC glGetStringi = nullptr;
PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB = nullptr;
PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB = nullptr;
#define glGetStringi details::glGetStringi
#define glDebugMessageControlARB details::glDebugMessageControlARB
#define glDebugMessageCallbackARB details::glDebugMessageCallbackARB
template<typename FunctionPtrT>
FunctionPtrT load_gl_function(const std::string& name)
{
// Try using wglGetProcAddress
FunctionPtrT address = reinterpret_cast<FunctionPtrT>(wglGetProcAddress(name.c_str()));
if (!address)
{
// Fallback to trying to find it in opengl32.dll
static std::unique_ptr<std::remove_pointer<HMODULE>::type, decltype(&FreeLibrary)> module(LoadLibrary(TEXT("opengl32.dll")), FreeLibrary);
address = reinterpret_cast<FunctionPtrT>(GetProcAddress(module.get(), name.c_str()));
}
return address;
}
#define TRY_LOAD_FUNCTION(name, type) name = load_gl_function<type>(#name)
void load_wgl_functions()
{
TRY_LOAD_FUNCTION(wglGetExtensionsStringEXT, PFNWGLGETEXTENSIONSSTRINGEXTPROC);
TRY_LOAD_FUNCTION(wglGetExtensionsStringARB, PFNWGLGETEXTENSIONSSTRINGARBPROC);
TRY_LOAD_FUNCTION(wglChoosePixelFormatEXT, PFNWGLCHOOSEPIXELFORMATEXTPROC);
TRY_LOAD_FUNCTION(wglChoosePixelFormatARB, PFNWGLCHOOSEPIXELFORMATARBPROC);
TRY_LOAD_FUNCTION(wglCreateContextAttribsARB, PFNWGLCREATECONTEXTATTRIBSARBPROC);
TRY_LOAD_FUNCTION(wglSwapIntervalEXT, PFNWGLSWAPINTERVALEXTPROC);
}
void load_gl_functions()
{
TRY_LOAD_FUNCTION(glGetStringi, PFNGLGETSTRINGIPROC);
TRY_LOAD_FUNCTION(glDebugMessageControlARB, PFNGLDEBUGMESSAGECONTROLARBPROC);
TRY_LOAD_FUNCTION(glDebugMessageCallbackARB, PFNGLDEBUGMESSAGECALLBACKARBPROC);
}
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// Extensions
// ----------------------------------------------------------------------------------------------------------------------------------------------------
#define HAS_EXTENSION(ext) details::ext##_state
// WGL
bool WGL_EXT_extensions_string_state = false;
bool WGL_ARB_extensions_string_state = false;
bool WGL_EXT_pixel_format_state = false;
bool WGL_ARB_pixel_format_state = false;
bool WGL_EXT_framebuffer_sRGB_state = false;
bool WGL_ARB_framebuffer_sRGB_state = false;
bool WGL_EXT_multisample_state = false;
bool WGL_ARB_multisample_state = false;
bool WGL_ARB_create_context_state = false;
bool WGL_ARB_create_context_profile_state = false;
bool WGL_ARB_create_context_no_error_state = false;
bool WGL_ARB_create_context_robustness_state = false;
bool WGL_EXT_swap_control_state = false;
bool WGL_EXT_swap_control_tear_state = false;
// OpenGL
bool GL_ARB_debug_output_state = false;
#define TRY_GET_EXTENSION(ext) ext##_state = extensions_string.find(#ext) != std::string::npos
void get_wgl_extensions(HDC hdc)
{
WGL_EXT_extensions_string_state = wglGetExtensionsStringEXT;
WGL_ARB_extensions_string_state = wglGetExtensionsStringARB;
// If no way of getting extensions exists, there are no WGL extensions enabled
if (!HAS_EXTENSION(WGL_ARB_extensions_string) && !HAS_EXTENSION(WGL_EXT_extensions_string))
return;
const char* extensions_ptr = nullptr;
if (wglGetExtensionsStringARB)
extensions_ptr = wglGetExtensionsStringARB(hdc);
else
extensions_ptr = wglGetExtensionsStringEXT();
if (!extensions_ptr)
return;
std::string extensions_string(extensions_ptr);
std::cout << "WGL extensions: " << extensions_string << "\n\n";
TRY_GET_EXTENSION(WGL_EXT_pixel_format);
TRY_GET_EXTENSION(WGL_ARB_pixel_format);
TRY_GET_EXTENSION(WGL_EXT_framebuffer_sRGB);
TRY_GET_EXTENSION(WGL_ARB_framebuffer_sRGB);
TRY_GET_EXTENSION(WGL_EXT_multisample);
TRY_GET_EXTENSION(WGL_ARB_multisample);
TRY_GET_EXTENSION(WGL_ARB_create_context);
TRY_GET_EXTENSION(WGL_ARB_create_context_profile);
TRY_GET_EXTENSION(WGL_ARB_create_context_no_error);
TRY_GET_EXTENSION(WGL_ARB_create_context_robustness);
TRY_GET_EXTENSION(WGL_EXT_swap_control);
TRY_GET_EXTENSION(WGL_EXT_swap_control_tear);
}
void get_gl_extensions()
{
std::string extensions_string;
if (glGetStringi)
{
int extension_count = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &extension_count);
for (int i = 0; i < extension_count; i++)
{
const GLubyte* extension = glGetStringi(GL_EXTENSIONS, i);
if (!extension)
break;
extensions_string += reinterpret_cast<const char*>(extension);
extensions_string += " ";
}
}
else
{
const GLubyte* extensions = glGetString(GL_EXTENSIONS);
if (!extensions)
{
std::cout << "glGetString returned nullptr for GL_EXTENSIONS.";
return;
}
extensions_string = reinterpret_cast<const char*>(extensions);
}
std::cout << "GL extensions: " << extensions_string << "\n\n";
TRY_GET_EXTENSION(GL_ARB_debug_output);
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// WGL Context Creation
// ----------------------------------------------------------------------------------------------------------------------------------------------------
enum class context_type_hints
{
any,
core,
compatibility
};
struct context_parameters
{
int major_version;
int minor_version;
bool double_buffering;
context_type_hints type_hint;
bool prefer_forward_compatibility;
bool prefer_debug;
bool prefer_robustness;
bool prefer_no_errors;
int samples_hint;
int color_bits;
int depth_bits;
int stencil_bits;
};
static HGLRC wgl_create_context_dispatch(HDC hdc, const context_parameters& parameters, bool dummy)
{
// Choose a pixel format
// wglChoosePixelFormatARB is preferred if extension is available, then comes wglChoosePixelFormatEXT and finally fall back to using ChoosePixelFormat.
// If it's the dummy context, we just choose the first format available, since it doesn't matter.
{
int pixel_format = 0;
PIXELFORMATDESCRIPTOR format_descriptor{};
if (!dummy && HAS_EXTENSION(WGL_ARB_pixel_format))
{
std::vector<int> attributes =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_SWAP_METHOD_ARB, WGL_SWAP_UNDEFINED_ARB,
WGL_DOUBLE_BUFFER_ARB, parameters.double_buffering ? GL_FALSE : GL_TRUE,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, parameters.color_bits,
WGL_DEPTH_BITS_ARB, parameters.depth_bits,
WGL_STENCIL_BITS_ARB, parameters.stencil_bits,
};
// These checks aren't really needed because the wglChoosePixelFormat* functions will ignore values for attributes
// which extensions aren't present but it certainly won't hurt if that changes in the future or if an implementation
// doesn't follow the spec.
// Check if extension for SRGB framebuffer is available and make use of it
if (HAS_EXTENSION(WGL_ARB_framebuffer_sRGB))
{
attributes.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB);
attributes.push_back(GL_TRUE);
}
// Check if extension for multisampling is available and make use of it
if (HAS_EXTENSION(WGL_ARB_multisample))
{
attributes.push_back(WGL_SAMPLE_BUFFERS_ARB);
attributes.push_back(parameters.samples_hint > 1 ? GL_TRUE : GL_FALSE),
attributes.push_back(WGL_SAMPLES_ARB);
attributes.push_back(parameters.samples_hint);
}
attributes.push_back(0); // Terminate attribute list
UINT format_count = 0;
if (!wglChoosePixelFormatARB(hdc, attributes.data(), nullptr, 1, &pixel_format, &format_count))
{
std::cout << "Failed to choose pixel format with wglChoosePixelFormatARB.\n";
return nullptr;
}
if (format_count == 0)
{
std::cout << "wglChoosePixelFormatARB failed to find a matching pixel format.\n";
return nullptr;
}
// Fill in the pixel format descriptor with the format info
if (!DescribePixelFormat(hdc, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &format_descriptor))
{
std::cout << "Failed to describe pixel format chosen by wglChoosePixelFormatEXT.\n";
return nullptr;
}
}
else if (!dummy && HAS_EXTENSION(WGL_EXT_pixel_format))
{
std::vector<int> attributes =
{
WGL_DRAW_TO_WINDOW_EXT, GL_TRUE,
WGL_SUPPORT_OPENGL_EXT, GL_TRUE,
WGL_SWAP_METHOD_EXT, WGL_SWAP_UNDEFINED_EXT,
WGL_DOUBLE_BUFFER_EXT, parameters.double_buffering ? GL_FALSE : GL_TRUE,
WGL_ACCELERATION_EXT, WGL_FULL_ACCELERATION_EXT,
WGL_PIXEL_TYPE_EXT, WGL_TYPE_RGBA_EXT,
WGL_COLOR_BITS_EXT, parameters.color_bits,
WGL_DEPTH_BITS_EXT, parameters.depth_bits,
WGL_STENCIL_BITS_EXT, parameters.stencil_bits,
};
if (HAS_EXTENSION(WGL_EXT_multisample))
{
attributes.push_back(WGL_SAMPLE_BUFFERS_EXT);
attributes.push_back(parameters.samples_hint > 1 ? GL_TRUE : GL_FALSE),
attributes.push_back(WGL_SAMPLES_EXT);
attributes.push_back(parameters.samples_hint);
}
if (HAS_EXTENSION(WGL_EXT_framebuffer_sRGB))
{
attributes.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT);
attributes.push_back(GL_TRUE);
}
attributes.push_back(0); // Terminate attribute list
UINT format_count = 0;
if (wglChoosePixelFormatEXT(hdc, attributes.data(), nullptr, 1, &pixel_format, &format_count) == FALSE)
{
std::cout << "Failed to choose pixel format with wglChoosePixelFormatEXT.\n";
return nullptr;
}
if (format_count == 0)
{
std::cout << "wglChoosePixelFormatEXT failed to find a matching pixel format.\n";
return nullptr;
}
// Fill in the pixel format descriptor with the format info
if (!DescribePixelFormat(hdc, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &format_descriptor))
{
std::cout << "Failed to describe pixel format chosen by wglChoosePixelFormatEXT.\n";
return nullptr;
}
}
else if (!dummy)
{
format_descriptor.nSize = sizeof(PIXELFORMATDESCRIPTOR);
format_descriptor.nVersion = 1;
format_descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
if (parameters.double_buffering)
format_descriptor.dwFlags |= PFD_DOUBLEBUFFER;
format_descriptor.iPixelType = PFD_TYPE_RGBA;
format_descriptor.cColorBits = parameters.color_bits;
format_descriptor.cDepthBits = parameters.depth_bits;
format_descriptor.cStencilBits = parameters.stencil_bits;
pixel_format = ChoosePixelFormat(hdc, &format_descriptor);
if (pixel_format == 0)
{
std::cout << "Failed to choose pixel format with ChoosePixelFormat.\n";
return nullptr;
}
}
else
{
// List the available formats (optional, just for information's sake)
{
int format_count = DescribePixelFormat(hdc, 0, 0, nullptr);
std::cout << "Available pixel formats: " << format_count << "\n";
std::cout << "Showing OpenGL window drawable ones.\n";
for (int i = 0; i < format_count; i++)
{
PIXELFORMATDESCRIPTOR format{};
DescribePixelFormat(hdc, i, sizeof(PIXELFORMATDESCRIPTOR), &format);
if (format.dwFlags & PFD_DRAW_TO_WINDOW && format.dwFlags & PFD_SUPPORT_OPENGL)
{
std::cout << "Color: " << static_cast<int>(format.cColorBits);
std::cout << " | Alpha: " << static_cast<int>(format.cAlphaBits);
std::cout << " | Depth: " << static_cast<int>(format.cDepthBits);
std::cout << " | Stencil: " << static_cast<int>(format.cStencilBits);
if (format.dwFlags & PFD_DOUBLEBUFFER)
std::cout << " | DoubleBuffered";
if (format.dwFlags & PFD_STEREO)
std::cout << " | Stereo";
if (format.dwFlags & PFD_DRAW_TO_BITMAP)
std::cout << " | Bitmap";
if (format.dwFlags & PFD_GENERIC_ACCELERATED)
std::cout << " | Generic";
if (format.dwFlags & PFD_SUPPORT_GDI)
std::cout << " | GDI";
if (format.dwFlags & PFD_SUPPORT_DIRECTDRAW)
std::cout << " | DirectDraw";
if (format.dwFlags & PFD_DIRECT3D_ACCELERATED)
std::cout << " | Direct3D";
if (format.dwFlags & PFD_NEED_PALETTE)
std::cout << " | Palette";
if (format.dwFlags & PFD_SUPPORT_COMPOSITION)
std::cout << " | Composition";
if (format.dwFlags & PFD_SWAP_LAYER_BUFFERS)
std::cout << " | Layers";
if (format.dwFlags & PFD_SWAP_COPY)
std::cout << " | SwapCopy";
if (format.dwFlags & PFD_SWAP_EXCHANGE)
std::cout << " | SwapExchange";
std::cout << "\n";
}
}
std::cout << "\n";
}
// Bare minimum format descriptor
format_descriptor.nSize = sizeof(PIXELFORMATDESCRIPTOR);
format_descriptor.nVersion = 1;
format_descriptor.dwFlags = PFD_SUPPORT_OPENGL;
pixel_format = ChoosePixelFormat(hdc, &format_descriptor);
if (pixel_format == 0)
{
std::cout << "Failed to choose dummy context pixel format with ChoosePixelFormat.\n";
return nullptr;
}
}
if (!SetPixelFormat(hdc, pixel_format, &format_descriptor))
{
std::cout << "Failed to set HDC pixel format.\n";
return nullptr;
}
}
// Create an OpenGL render context
// wglCreateContextAttribsARB is preferred if the extension is present. If not or we are creating the dummy context, we fall back to wglCreateContext
HGLRC hglrc = nullptr;
{
if (!dummy && HAS_EXTENSION(WGL_ARB_create_context))
{
std::vector<int> attributes;
if (parameters.major_version > 1)
{
attributes.push_back(WGL_CONTEXT_MAJOR_VERSION_ARB);
attributes.push_back(parameters.major_version);
attributes.push_back(WGL_CONTEXT_MINOR_VERSION_ARB);
attributes.push_back(parameters.minor_version);
}
// The extension checks here are also optional but used as a precaution
if (parameters.type_hint != context_type_hints::any && HAS_EXTENSION(WGL_ARB_create_context_profile))
{
attributes.push_back(WGL_CONTEXT_PROFILE_MASK_ARB);
if (parameters.type_hint == context_type_hints::core)
attributes.push_back(WGL_CONTEXT_CORE_PROFILE_BIT_ARB);
else
attributes.push_back(WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB);
}
if (parameters.prefer_no_errors && HAS_EXTENSION(WGL_ARB_create_context_no_error))
{
attributes.push_back(WGL_CONTEXT_OPENGL_NO_ERROR_ARB);
attributes.push_back(GL_TRUE);
}
int context_flags = 0;
if (parameters.prefer_forward_compatibility)
context_flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
else if (parameters.prefer_debug)
context_flags |= WGL_CONTEXT_DEBUG_BIT_ARB;
else if (parameters.prefer_robustness && HAS_EXTENSION(WGL_ARB_create_context_robustness))
context_flags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB;
if (context_flags != 0)
{
attributes.push_back(WGL_CONTEXT_FLAGS_ARB);
attributes.push_back(context_flags);
}
if (parameters.prefer_robustness && HAS_EXTENSION(WGL_ARB_create_context_robustness))
{
attributes.push_back(WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB);
attributes.push_back(WGL_LOSE_CONTEXT_ON_RESET_ARB);
}
attributes.push_back(0); // Terminate attribute list
hglrc = wglCreateContextAttribsARB(hdc, nullptr, attributes.data());
if (!hglrc)
{
std::cout << "Failed to create context with wglCreateContextAttribsARB.\n";
return nullptr;
}
}
else
{
hglrc = wglCreateContext(hdc);
}
}
return hglrc;
}
// ----------------------------------------------------------------------------------------------------------------------------------------------------
// Application
// ----------------------------------------------------------------------------------------------------------------------------------------------------
using wnd_proc_t = decltype(&DefWindowProc);
static bool register_class(const std::basic_string<TCHAR>& name, wnd_proc_t wnd_proc)
{
WNDCLASS cls{};
cls.hInstance = GetModuleHandle(nullptr);
cls.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
cls.lpfnWndProc = wnd_proc;
cls.lpszClassName = name.c_str();
if (!RegisterClass(&cls))
return false;
return true;
}
static bool load_opengl(const context_parameters& parameters)
{
// Create a class for the bootstrapping dummy with a very basic WndProc
bool result = register_class(g_dummy_class_name, [](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { return DefWindowProc(hwnd, msg, wparam, lparam); });
if (!result)
{
std::cout << "Failed to register dummy window class.\n";
return false;
}
// Create a bare minimum window for the dummy
HWND window = CreateWindow(g_dummy_class_name.c_str(), nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, 0);
if (!window)
{
std::cout << "Failed to create dummy window.\n";
return false;
}
// Get the device context
HDC dummy_dc = GetDC(window);
if (!dummy_dc)
{
std::cout << "Failed to get dummy window dc.\n";
return false;
}
// Create an OpenGL render context
HGLRC dummy_context = wgl_create_context_dispatch(dummy_dc, parameters, true);
if (!dummy_context)
{
std::cout << "Failed to create dummy context.\n";
return false;
}
// Temporarily make it "current" just so we can extract the OpenGL information we need
if (wglMakeCurrent(dummy_dc, dummy_context) == FALSE)
{
std::cout << "Failed to make dummy context current.\n";
return false;
}
// Get the information
details::load_wgl_functions();
details::get_wgl_extensions(dummy_dc);
details::load_gl_functions();
details::get_gl_extensions();
// Dispose of the dummy context and all associated resources
wglMakeCurrent(dummy_dc, nullptr);
wglDeleteContext(dummy_context);
DestroyWindow(window);
UnregisterClass(g_dummy_class_name.c_str(), nullptr);
return true;
}
static HGLRC create_context(HDC hdc, const context_parameters& parameters)
{
// Load OpenGL only once
static bool gl_loaded = false;
if (!gl_loaded)
{
if (!load_opengl(parameters))
{
std::cout << "Failed to load OpenGL.\n";
return nullptr;
}
gl_loaded = true;
}
// Create context
HGLRC context = wgl_create_context_dispatch(hdc, parameters, false);
if (!context)
{
std::cout << "Failed to create context.\n";
return nullptr;
}
return context;
}
enum class swap_control_types
{
dont_care,
forced,
relaxed,
};
struct render_parameters
{
HDC hdc;
HGLRC hglrc;
int viewport_width;
int viewport_height;
swap_control_types swap_control_hint;
int swap_interval_hint;
};
static void __stdcall gl_debug_proc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
std::cout << "OpenGL Error " << id << ": " << message << std::endl;
}
static std::atomic<bool> s_render_canceled;
static void render_thread_main(const render_parameters& parameters)
{
// Make the render context current in this thread
wglMakeCurrent(parameters.hdc, parameters.hglrc);
int flags;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT && HAS_EXTENSION(GL_ARB_debug_output))
{
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
glDebugMessageCallbackARB(&gl_debug_proc, nullptr);
glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
// Use the swap control hint to decide what interval value to use
// Relaxed swapping means frames are allowed to arrive late and reduces stuttering when FPS is below the refresh rate.
// It is activated by calling wglSwapIntervalEXT with a negative interval (only available if the extensions WGL_EXT_swap_control and WGL_EXT_swap_control_tear are present).
// Forced swapping means a locked frame pacing which will stutter when FPS is below the refresh rate.
// It is activated by calling wglSwapIntervalEXT with a positive interval (only available if the extension WGL_EXT_swap_control is present)
if (parameters.swap_control_hint != swap_control_types::dont_care)
{
if (HAS_EXTENSION(WGL_EXT_swap_control))
{
if (parameters.swap_control_hint == swap_control_types::relaxed)
{
if (HAS_EXTENSION(WGL_EXT_swap_control_tear))
{
wglSwapIntervalEXT(-parameters.swap_interval_hint);
}
else
{
std::cout << "Tried setting relaxed v-sync but the required extension isn't present. Reverting to forced v-sync.";
wglSwapIntervalEXT(parameters.swap_interval_hint);
}
}
else
{
wglSwapIntervalEXT(parameters.swap_interval_hint);
}
}
else
{
std::cout << "Tried setting v-sync but the required extension isn't present. No swap control can be done.";
}
}
// Enable sRGB framebuffer if it's available
if (HAS_EXTENSION(WGL_ARB_framebuffer_sRGB))
glEnable(GL_FRAMEBUFFER_SRGB);
else if (HAS_EXTENSION(WGL_EXT_framebuffer_sRGB))
glEnable(GL_FRAMEBUFFER_SRGB_EXT);
// Set rasterizer viewport state
glViewport(0, 0, parameters.viewport_width, parameters.viewport_height);
// Print out some relevant information about the context
std::cout << "Version: " << glGetString(GL_VERSION) << "\n";
std::cout << "Renderer: " << glGetString(GL_RENDERER) << "\n";
std::cout << "Vendor: " << glGetString(GL_VENDOR) << "\n";
std::cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << "\n";
// Render loop
while (!s_render_canceled)
{
// TODO: Do a robustness check here if the robustness context flag is present. I can't test this since AMD hasn't implemented robust contexts.
// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
// Immediate mode "Hello World" triangle drawing
// Don't use immediate mode for modern tasks. This test is using it as a demonstration because it's targetting OpenGL 2.0, a version that is low enough
// not to require a lot of extension checking/function loading.
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(-1.0f, -1.0f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(1.0f, -1.0f);
glEnd();
// Swap buffers and flush the OpenGL commands
SwapBuffers(parameters.hdc);
}
}
int main(int argc, char* argv[])
{
// Register the main window class
{
bool result = register_class(g_class_name, [](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT
{
switch (msg)
{
case WM_CLOSE:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
});
if (!result)
{
std::cout << "Failed to register main window class.\n";
return -1;
}
}
// Adjust the window rect so our window's client size is *exactly* the size we specify (excluding the window borders)
DWORD window_style = WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
RECT window_rect = {0, 0, g_window_width, g_window_height};
if (!AdjustWindowRect(&window_rect, window_style, FALSE))
{
std::cout << "Failed to adjust the window size.\n";
return false;
}
// Create the window
HWND window = CreateWindow(g_class_name.c_str(), g_window_title.c_str(), window_style, CW_USEDEFAULT, CW_USEDEFAULT, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, nullptr, nullptr, nullptr, 0);
if (!window)
{
std::cout << "Failed to create main window.\n";
return false;
}
// Show the window
ShowWindow(window, SW_NORMAL);
// Get the device context
HDC window_dc = GetDC(window);
if (!window_dc)
{
std::cout << "Failed to get main window dc.\n";
return -1;
}
// Create OpenGL context
HGLRC main_context;
{
context_parameters parameters;
parameters.major_version = 2;
parameters.minor_version = 0;
parameters.type_hint = context_type_hints::compatibility;
parameters.prefer_forward_compatibility = false;
parameters.prefer_debug = false;
parameters.prefer_robustness = false;
parameters.prefer_no_errors = true;
parameters.samples_hint = 4;
parameters.double_buffering = true;
parameters.color_bits = 32;
parameters.depth_bits = 24;
parameters.stencil_bits = 8;
main_context = create_context(window_dc, parameters);
if (!main_context)
{
std::cout << "Failed to create OpenGL context.\n";
return -1;
}
}
// Launch render thread
render_parameters parameters;
parameters.hdc = window_dc;
parameters.hglrc = main_context;
parameters.swap_control_hint = swap_control_types::relaxed;
parameters.swap_interval_hint = 1;
parameters.viewport_width = g_window_width;
parameters.viewport_height = g_window_height;
std::thread render_thread(render_thread_main, parameters);
// Main handle is now in charge of processing the window's blocking message loop
MSG message;
while (GetMessage(&message, nullptr, 0, 0) > 0)
{
TranslateMessage(&message);
DispatchMessage(&message);
}
// Call for the exit of the rendering thread and wait for it to exit gracefully
s_render_canceled = true;
render_thread.join();
// Clean up all resources
wglMakeCurrent(window_dc, nullptr);
wglDeleteContext(main_context);
DestroyWindow(window);
UnregisterClass(g_class_name.c_str(), nullptr);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment