Implement OpenGL from window-starter

The bulk of the work to create an OpenGL context on windows and load the some of the functions required to get a triangle rendering using modern OpenGL
This commit is contained in:
McMassiveNZ
2023-05-08 18:57:16 +02:00
parent 34126c21a2
commit f8a10e126f
15 changed files with 21517 additions and 58 deletions

View File

@@ -1,31 +1,37 @@
set(current_target starter_window)
set(current_target opengl-starter)
set(SOURCE_FILES)
set(STARTER_WINDOW_SRC
set(OPENGL_STARTER_SRC
main.cpp
window.h
opengl.h
)
source_group("" FILES ${STARTER_WINDOW_SRC})
source_group("" FILES ${OPENGL_STARTER_SRC})
if(MSVC)
set(PLATFORM_SRC
platform/win32_window.cpp
platform/win32_opengl.cpp
)
else()
set(PLATFORM_SRC
platform/null_window.cpp
platform/null_opengl.cpp
)
endif()
source_group(platform FILES ${PLATFORM_SRC})
list(APPEND SOURCE_FILES ${PLATFORM_SRC})
list(APPEND SOURCE_FILES ${STARTER_WINDOW_SRC})
list(APPEND SOURCE_FILES ${OPENGL_STARTER_SRC})
add_executable(
${current_target}
${SOURCE_FILES}
)
target_include_directories(${current_target} PUBLIC ${CMAKE_SOURCE_DIR}/external)
target_link_libraries(${current_target} PUBLIC opengl32.lib)
if( ENABLE_ALL_REASONABLE_WARNINGS )
MESSAGE("-- Additional Warnings Enabled")
target_enable_warnings(${current_target})

View File

@@ -1,20 +1,32 @@
#include "window.h"
#include "opengl.h"
constexpr int CW_USEDEFAULT = 0x80000000;
auto main() -> int
{
auto window = swCreateWindow({
auto window = oglsCreateWindow({
.x = CW_USEDEFAULT,
.y = CW_USEDEFAULT,
.width = CW_USEDEFAULT,
.height = CW_USEDEFAULT,
.name = "Starter Window"
.name = "Starter OpenGL"
});
auto renderer = oglsCreateOpenGL({
.nativeWindowHandle = window->GetNativeHandle(),
.versionMajor = 4,
.versionMinor = 6
});
while (!window->ShouldClose())
{
window->PumpMessages();
}
}
renderer->ClearBuffers();
renderer->DrawScene();
renderer->SwapBuffers();
}
return 0;
}

28
src/opengl.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <memory>
namespace ogl_starter
{
class Window;
struct OpenGLCreateParams
{
void* nativeWindowHandle;
int versionMajor;
int versionMinor;
};
class OpenGL
{
public:
virtual ~OpenGL() = default;
virtual void ClearBuffers() = 0;
virtual void SwapBuffers() = 0;
virtual void DrawScene() = 0;
};
} // namespace ogl_starter
std::unique_ptr<ogl_starter::OpenGL> oglsCreateOpenGL(ogl_starter::OpenGLCreateParams params);

View File

@@ -0,0 +1,25 @@
#include "../opengl.h"
namespace ogl_starter
{
class NullOpenGLImpl final : public OpenGL
{
public:
~NullOpenGLImpl() override = default;
NullOpenGLImpl(const NullOpenGLImpl&) = delete;
NullOpenGLImpl& operator=(const NullOpenGLImpl&) = delete;
NullOpenGLImpl(NullOpenGLImpl&&) = default;
NullOpenGLImpl& operator=(NullOpenGLImpl&&) = default;
void ClearBuffers() override {}
void SwapBuffers() override {}
void DrawScene() override {}
};
} // namespace ogl_starter
std::unique_ptr<ogl_starter::Window> oglsCreateWindow(ogl_starter::OpenGLCreateParams)
{
auto result = std::make_unique<ogl_starter::NullOpenGLImpl>();
return result;
}

View File

@@ -1,6 +1,6 @@
#include "../window.h"
namespace starter_window
namespace ogl_starter
{
class NullWindowImpl final : public Window
@@ -21,8 +21,8 @@ public:
} // namespace starter_window
std::unique_ptr<starter_window::Window> swCreateWindow(starter_window::WindowCreateParams)
std::unique_ptr<ogl_starter::Window> oglsCreateWindow(ogl_starter::WindowCreateParams)
{
auto result = std::make_unique<starter_window::NullWindowImpl>();
auto result = std::make_unique<ogl_starter::NullWindowImpl>();
return result;
}

View File

@@ -0,0 +1,393 @@
#include "../opengl.h"
#include "../window.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#include <GL/GL.h>
#include "GL/glext.h"
#include "GL/wglext.h"
template <typename T>
static T LoadGLProc(const char* functionName)
{
FARPROC proc = wglGetProcAddress(functionName);
return static_cast<T>(static_cast<void*>(proc));
}
#define VALIDATEGLPROC(proc) \
if (!proc) \
{ \
MessageBox(NULL, "Failed to load " #proc, "Fatal Error", MB_ICONERROR); \
return false; \
}
static PFNWGLGETEXTENSIONSSTRINGEXTPROC wglGetExtensionsStringEXT;
static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
static PFNGLDEBUGMESSAGECALLBACKPROC glDebugMessageCallback;
static PFNGLCREATEPROGRAMPROC glCreateProgram;
static PFNGLDELETEPROGRAMPROC glDeleteProgram;
static PFNGLATTACHSHADERPROC glAttachShader;
static PFNGLLINKPROGRAMPROC glLinkProgram;
static PFNGLUSEPROGRAMPROC glUseProgram;
static PFNGLGETPROGRAMIVPROC glGetProgramiv;
static PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
static PFNGLCREATESHADERPROC glCreateShader;
static PFNGLDELETESHADERPROC glDeleteShader;
static PFNGLCOMPILESHADERPROC glCompileShader;
static PFNGLSHADERSOURCEPROC glShaderSource;
static PFNGLGETSHADERIVPROC glGetShaderiv;
static PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
static PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
static PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
static PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
static PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
static PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
static PFNGLGENBUFFERSPROC glGenBuffers;
static PFNGLBINDBUFFERPROC glBindBuffer;
static PFNGLBUFFERDATAPROC glBufferData;
namespace ogl_starter
{
static void LoadGLFunctions()
{
glDebugMessageCallback = LoadGLProc<PFNGLDEBUGMESSAGECALLBACKPROC>("glDebugMessageCallback");
glCreateProgram = LoadGLProc<PFNGLCREATEPROGRAMPROC>("glCreateProgram");
glDeleteProgram = LoadGLProc<PFNGLDELETEPROGRAMPROC>("glDeleteProgram");
glAttachShader = LoadGLProc<PFNGLATTACHSHADERPROC>("glAttachShader");
glLinkProgram = LoadGLProc<PFNGLLINKPROGRAMPROC>("glLinkProgram");
glUseProgram = LoadGLProc<PFNGLUSEPROGRAMPROC>("glUseProgram");
glGetProgramiv = LoadGLProc<PFNGLGETPROGRAMIVPROC>("glGetProgramiv");
glGetProgramInfoLog = LoadGLProc<PFNGLGETPROGRAMINFOLOGPROC>("glGetProgramInfoLog");
glCreateShader = LoadGLProc<PFNGLCREATESHADERPROC>("glCreateShader");
glDeleteShader = LoadGLProc<PFNGLDELETESHADERPROC>("glDeleteShader");
glCompileShader = LoadGLProc<PFNGLCOMPILESHADERPROC>("glCompileShader");
glShaderSource = LoadGLProc<PFNGLSHADERSOURCEPROC>("glShaderSource");
glGetShaderiv = LoadGLProc<PFNGLGETSHADERIVPROC>("glGetShaderiv");
glGetShaderInfoLog = LoadGLProc<PFNGLGETSHADERINFOLOGPROC>("glGetShaderInfoLog");
glGenVertexArrays = LoadGLProc<PFNGLGENVERTEXARRAYSPROC>("glGenVertexArrays");
glDeleteVertexArrays = LoadGLProc<PFNGLDELETEVERTEXARRAYSPROC>("glDeleteVertexArrays");
glBindVertexArray = LoadGLProc<PFNGLBINDVERTEXARRAYPROC>("glBindVertexArray");
glVertexAttribPointer = LoadGLProc<PFNGLVERTEXATTRIBPOINTERPROC>("glVertexAttribPointer");
glEnableVertexAttribArray = LoadGLProc<PFNGLENABLEVERTEXATTRIBARRAYPROC>("glEnableVertexAttribArray");
glGenBuffers = LoadGLProc<PFNGLGENBUFFERSPROC>("glGenBuffers");
glBindBuffer = LoadGLProc<PFNGLBINDBUFFERPROC>("glBindBuffer");
glBufferData = LoadGLProc<PFNGLBUFFERDATAPROC>("glBufferData");
}
class Win32OpenGLImpl final : public OpenGL
{
public:
Win32OpenGLImpl();
~Win32OpenGLImpl() override;
Win32OpenGLImpl(const Win32OpenGLImpl&) = delete;
Win32OpenGLImpl& operator=(const Win32OpenGLImpl&) = delete;
Win32OpenGLImpl(Win32OpenGLImpl&&) = default;
Win32OpenGLImpl& operator=(Win32OpenGLImpl&&) = default;
bool init(OpenGLCreateParams params);
void ClearBuffers() override;
void SwapBuffers() override;
void DrawScene() override;
private:
void initTriangleResources();
HDC m_deviceContext;
HGLRC m_renderingContext;
HWND m_windowHandle;
//TEMP STUFF
GLuint VAO;
GLuint shaderProgram;
};
Win32OpenGLImpl::Win32OpenGLImpl()
: m_deviceContext(nullptr)
, m_renderingContext(nullptr)
, m_windowHandle(nullptr)
, VAO(0)
, shaderProgram(0)
{
}
Win32OpenGLImpl::~Win32OpenGLImpl()
{
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(m_renderingContext);
}
bool Win32OpenGLImpl::init(OpenGLCreateParams params)
{
m_windowHandle = static_cast<HWND>(params.nativeWindowHandle);
HINSTANCE hInstance = GetModuleHandle(NULL);
HWND dummyWindow = [hInstance]() -> HWND
{
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(wcex));
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = "DummyWNDClass";
if (RegisterClassEx(&wcex) == 0)
{
MessageBox(NULL, "registerClass() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
return CreateWindow(
wcex.lpszClassName, "Dummy Window",
WS_OVERLAPPEDWINDOW,
0, 0,
1, 1,
nullptr, nullptr,
hInstance, nullptr);
}();
HDC dummyDC = GetDC(dummyWindow);
PIXELFORMATDESCRIPTOR dummyPFD = {};
dummyPFD.nSize = sizeof(dummyPFD);
dummyPFD.nVersion = 1;
dummyPFD.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
dummyPFD.iPixelType = PFD_TYPE_RGBA;
dummyPFD.cColorBits = 32;
dummyPFD.cAlphaBits = 8;
dummyPFD.cDepthBits = 24;
dummyPFD.iLayerType = PFD_MAIN_PLANE;
const int dummyPFDID = ChoosePixelFormat(dummyDC, &dummyPFD);
if (dummyPFDID == 0)
{
MessageBox(nullptr, "ChoosePixelFormat() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
if (SetPixelFormat(dummyDC, dummyPFDID, &dummyPFD) == false)
{
MessageBox(nullptr, "SetPixelFormat() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
HGLRC dummyRC = wglCreateContext(dummyDC);
if (dummyRC == 0)
{
MessageBox(nullptr, "wglCreateContext() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
if (wglMakeCurrent(dummyDC, dummyRC) == false)
{
MessageBox(nullptr, "wglMakeCurrent() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
wglChoosePixelFormatARB = LoadGLProc<PFNWGLCHOOSEPIXELFORMATARBPROC>("wglChoosePixelFormatARB");
VALIDATEGLPROC(wglChoosePixelFormatARB);
wglCreateContextAttribsARB = LoadGLProc<PFNWGLCREATECONTEXTATTRIBSARBPROC>("wglCreateContextAttribsARB");
VALIDATEGLPROC(wglCreateContextAttribsARB);
m_deviceContext = GetDC(m_windowHandle);
const int pixelAttribs[] = {
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
WGL_SAMPLES_ARB, 4,
0};
int pixelFormatID;
UINT numFormats;
const bool status = wglChoosePixelFormatARB(m_deviceContext, pixelAttribs, NULL, 1, &pixelFormatID, &numFormats);
if (status == false || numFormats == 0)
{
MessageBox(nullptr, "wglChoosePixelFormatARB() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
PIXELFORMATDESCRIPTOR PFD;
DescribePixelFormat(m_deviceContext, pixelFormatID, sizeof(PFD), &PFD);
SetPixelFormat(m_deviceContext, pixelFormatID, &PFD);
const int major_min = 4, minor_min = 6;
const int contextAttribs[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, major_min,
WGL_CONTEXT_MINOR_VERSION_ARB, minor_min,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
WGL_CONTEXT_LAYER_PLANE_ARB, 0,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB,
0};
m_renderingContext = wglCreateContextAttribsARB(m_deviceContext, 0, contextAttribs);
if (m_renderingContext == nullptr)
{
MessageBox(nullptr, "wglCreateContextAttribsARB() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(dummyRC);
ReleaseDC(dummyWindow, dummyDC);
DestroyWindow(dummyWindow);
if (!wglMakeCurrent(m_deviceContext, m_renderingContext))
{
MessageBox(nullptr, "wglMakeCurrent() failed.", "Fatal Error", MB_ICONERROR);
return false;
}
LoadGLFunctions();
initTriangleResources();
return true;
}
void Win32OpenGLImpl::ClearBuffers()
{
glClearColor(0.129f, 0.586f, 0.949f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
void Win32OpenGLImpl::SwapBuffers()
{
::SwapBuffers(m_deviceContext);
}
void Win32OpenGLImpl::initTriangleResources()
{
float vertices[] = {
0.5f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f
};
unsigned int indices[] = {
0, 1, 3,
1, 2, 3
};
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
auto VS = glCreateShader(GL_VERTEX_SHADER);
auto FS = glCreateShader(GL_FRAGMENT_SHADER);
const char* vs_source =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
glShaderSource(VS, 1, &vs_source, nullptr);
glCompileShader(VS);
int success;
char infoLog[512];
glGetShaderiv(VS, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(VS, 512, nullptr, infoLog);
printf("Vertex Shader Compilation Failed\n %s\n", infoLog);
}
auto fs_source =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\0";
glShaderSource(FS, 1, &fs_source, nullptr);
glCompileShader(FS);
glGetShaderiv(FS, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(VS, 512, nullptr, infoLog);
printf("Fragment Shader Compilation Failed\n %s\n", infoLog);
}
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, VS);
glAttachShader(shaderProgram, FS);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
printf("Shader Program Link Failed\n %s\n", infoLog);
}
glUseProgram(shaderProgram);
glDeleteShader(VS);
glDeleteShader(FS);
}
void Win32OpenGLImpl::DrawScene()
{
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
}
} // namespace ogl_starter
std::unique_ptr<ogl_starter::OpenGL> oglsCreateOpenGL(ogl_starter::OpenGLCreateParams params)
{
auto result = std::make_unique<ogl_starter::Win32OpenGLImpl>();
if (!result->init(params))
{
result = nullptr;
}
return result;
}

View File

@@ -14,39 +14,34 @@ static LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM wParam, LPA
{
switch (message)
{
case WM_DESTROY:
case WM_CLOSE:
{
PostQuitMessage(0);
return 0;
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps = {};
HDC hdc = BeginPaint(window, &ps);
FillRect(hdc, &ps.rcPaint, reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1));
EndPaint(window, &ps);
return 0;
}
default:
return DefWindowProc(window, message, wParam, lParam);
}
return DefWindowProc(window, message, wParam, lParam);
return 0;
}
namespace starter_window
namespace ogl_starter
{
class Win32WindowImpl final : public Window
{
public:
Win32WindowImpl();
~Win32WindowImpl() override = default;
Win32WindowImpl(const Win32WindowImpl&) = delete;
Win32WindowImpl& operator=(const Win32WindowImpl&) = delete;
bool init(WindowCreateParams params);
void PumpMessages() override;
bool ShouldClose() override;
bool ShouldClose() const override;
void* GetNativeHandle() const override;
HINSTANCE hInstance;
HWND hWnd;
@@ -62,15 +57,13 @@ Win32WindowImpl::Win32WindowImpl()
bool Win32WindowImpl::init(WindowCreateParams params)
{
const char className[] = "Win32WindowImpl";
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = className;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = "WindowClass";
if (RegisterClassEx(&wc) == NULL)
{
@@ -78,51 +71,55 @@ bool Win32WindowImpl::init(WindowCreateParams params)
return false;
}
HWND window = CreateWindowEx(
0,
className,
hWnd = CreateWindow(
wc.lpszClassName,
params.name,
WS_OVERLAPPEDWINDOW,
params.x, params.y, params.width, params.height,
NULL,
NULL,
nullptr,
nullptr,
hInstance,
NULL);
nullptr);
if (window == NULL)
if (hWnd == nullptr)
{
MessageBox(nullptr, "Call to CreateWindow failed", NULL, MB_OK);
MessageBox(nullptr, "Call to CreateWindow failed", nullptr, MB_OK);
return false;
}
ShowWindow(window, SW_SHOW);
ShowWindow(hWnd, SW_SHOW);
return true;
}
void Win32WindowImpl::PumpMessages()
{
MSG message = {};
if (GetMessage(&message, NULL, 0, 0) != 0)
MSG msg = {};
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
else
{
// GetMessage returned WM_QUIT
m_close = true;
if (msg.message == WM_QUIT)
{
m_close = true;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
bool Win32WindowImpl::ShouldClose()
bool Win32WindowImpl::ShouldClose() const
{
return m_close;
}
} // namespace starter_window
std::unique_ptr<starter_window::Window> swCreateWindow(starter_window::WindowCreateParams params)
void* Win32WindowImpl::GetNativeHandle() const
{
auto result = std::make_unique<starter_window::Win32WindowImpl>();
return static_cast<void*>(hWnd);
}
} // namespace ogl_starter
std::unique_ptr<ogl_starter::Window> oglsCreateWindow(ogl_starter::WindowCreateParams params)
{
auto result = std::make_unique<ogl_starter::Win32WindowImpl>();
if (result->init(params) == false)
{
result = nullptr;

View File

@@ -2,7 +2,7 @@
#include <memory>
namespace starter_window
namespace ogl_starter
{
struct WindowCreateParams
@@ -19,9 +19,16 @@ class Window
public:
virtual ~Window() = default;
virtual void PumpMessages() = 0;
virtual bool ShouldClose() = 0;
virtual bool ShouldClose() const = 0;
virtual void* GetNativeHandle() const = 0;
template< typename T >
T NativeHandle() const
{
return static_cast<T>(GetNativeHandle());
}
};
}
std::unique_ptr<starter_window::Window> swCreateWindow(starter_window::WindowCreateParams params);
std::unique_ptr<ogl_starter::Window> oglsCreateWindow(ogl_starter::WindowCreateParams params);