C++ OpenGL 3D Game Tutorial 2: Making OpenGL 3D Engine study notes

Video address icon-default.png?t=N7T8https://www.youtube.com/watch?v=PH5kH8h82L8&list=PLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg&index=3

1. Main class

        Continuing from the previous article, some code has been added to main.cpp to make it more rigorous:

#include<OGL3D/Game/OGame.h>
#include<iostream>

int main()
{
	try {
		OGame game;
		game.Run();
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
		return 1;
	}

	return 0;
}

2. OGame category 

       Then the Game class adds a lot of content:

        Game.h file:

#pragma once
#include<memory>

class OGraphicsEngine;
class OWindow;
class OGame
{
public:
	OGame();
	~OGame();

	virtual void onCreate();
	virtual void onUpdate();
	virtual void onQuit();

	void Run();
protected:
	bool m_isRunning = true;
	std::unique_ptr<OGraphicsEngine> m_graphicsEngine;
	std::unique_ptr<OWindow> m_display;
};

        Game.cpp file:

#include<OGL3D/Graphics/OGraphicsEngine.h>
#include<OGL3D/Window/OWindow.h>
#include<OGL3D/Game/OGame.h>
#include<Windows.h>

OGame::OGame()
{
	m_graphicsEngine = std::make_unique<OGraphicsEngine>();
	m_display = std::make_unique<OWindow>();

	m_display->makeCurrentContext();
}

OGame::~OGame()
{
}

void OGame::onCreate()
{
	m_graphicsEngine->clear(OVec4(1,0,0,1));

	m_display->present(false);
}

void OGame::onUpdate()
{
}

void OGame::onQuit()
{
}

void OGame::Run()
{
	onCreate();

	MSG msg;
	while (m_isRunning)
	{
		msg = {};
		if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
			{
				m_isRunning = false;
				continue;
			}
			else 
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		onUpdate();
	}

	onQuit();
}

       Compared with the previous article, the OGraphicEngine object is created in the constructor, and the method makeCurrentContext of the OWindow object m_display is executed at the same time. This method is also added to the OWindow class in this video. It should be noted that the order of these two lines of code cannot be wrong:

m_graphicsEngine = std::make_unique<OGraphicsEngine>();
m_display = std::make_unique<OWindow>();

         Because the content in HDC needs to be obtained during OWindow construction, and this HDC content must be set during OGraphicsEngine construction, the OGraphicsEngine object must be constructed first, and reversing the order will definitely not work.

        At the same time, the onCreate, onUpdate, and onQuit methods are also added to the OGame class. Just pay attention to the calling timing of these three methods.

3. OWindow class

Owindow.h file

#pragma once

#include<Windows.h>
class OWindow
{
public:
		OWindow();
		~OWindow();

		void makeCurrentContext();
		void present(bool vsync);
private:
	HWND m_handle = nullptr;
	HGLRC m_context = nullptr;
};

OWindow.cpp file

#include<OGL3D/Window/OWindow.h>
#include<glad/glad.h>
#include<glad/glad_wgl.h>
#include <Windows.h>
#include<assert.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_DESTROY:
	{
		OWindow* window = (OWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
		break;
	}
	case WM_CLOSE:
	{
		PostQuitMessage(0);
		break;
	}
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
	return NULL;
}

OWindow::OWindow()
{
	WNDCLASSEX wc = {};
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpszClassName = L"OGL3DWindow";
	wc.lpfnWndProc = WndProc;

	auto classId = RegisterClassEx(&wc);
	assert(classId);

	RECT rc = { 0,0,1024,768 };
	AdjustWindowRect(&rc, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, false);

	m_handle = CreateWindowEx(NULL,
		MAKEINTATOM(classId),
		L"Parcode | OpenGL 3D Game",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		rc.right - rc.left, rc.bottom - rc.top,
		NULL, NULL, NULL, NULL);

	assert(m_handle);

	SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)this);

	ShowWindow(m_handle, SW_SHOW);
	UpdateWindow(m_handle);

	HDC hDC = GetDC(m_handle);

	int pixelFormatAttributes[] = {
		WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
		WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
		WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
		WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
		WGL_PIXEL_TYPE_ARB,WGL_TYPE_RGBA_ARB,
		WGL_COLOR_BITS_ARB,24,
		WGL_DEPTH_BITS_ARB,24,
		WGL_STENCIL_BITS_ARB,8,
		0
	};

	int iPixelFormat = 0;
	UINT numFormats = 0;
	wglChoosePixelFormatARB(hDC, pixelFormatAttributes, nullptr, 1, &iPixelFormat, &numFormats);
	assert(numFormats);

	PIXELFORMATDESCRIPTOR pixelFormatDesc = {};
	DescribePixelFormat(hDC, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pixelFormatDesc);
	SetPixelFormat(hDC, iPixelFormat, &pixelFormatDesc);

	int openAttributes[] = {
	WGL_CONTEXT_MAJOR_VERSION_ARB,4,
	WGL_CONTEXT_MINOR_VERSION_ARB,6,
	WGL_CONTEXT_PROFILE_MASK_ARB,WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
	0
	};

	m_context = wglCreateContextAttribsARB(hDC, 0, openAttributes);
	assert(m_context);
}

OWindow::~OWindow()
{
	wglDeleteContext(m_context);
	DestroyWindow(m_handle);
}

void OWindow::makeCurrentContext()
{
	wglMakeCurrent(GetDC(m_handle), m_context);
}

void OWindow::present(bool vsync)
{
	wglSwapIntervalEXT(vsync);
	wglSwapLayerBuffers(GetDC(m_handle), WGL_SWAP_MAIN_PLANE);
}

Create window

        Some issues regarding window creation need to be explained here. In fact, they should have been explained in the previous document, but the understanding was not in place at the time, so here is a supplement. 
        WNDCLASSEX creates a structure instance named wc. This instance is equivalent to us writing an application form for the type of window to be created in advance. The RegisterClass function submits (registers) the wc application form to the Window system, and then creates a window when needed. , the Window system will create it according to this application form.
        The formal parameter of RegisterClass is a pointer to the WNDCLASSEX structure. This pointer will be added to the system atom table, that is, SAT, so that the system can find the user-defined window class by searching this table. The window predefined window class pointer is also on the SAT.

        SAT actually implements a mapping for querying. The actual type of ATOM (translated as "atom") is short, which is a 16 integer. The ATOM table (atomic table) is a system-defined table used to store strings and corresponding identifiers. The program puts a string into the ATOM table and obtains a corresponding 16-bit integer. This integer is called an atom and can be used to access the string. A string that is put into the atom table is called the atom name.

        Only the system can directly access this table, but when calling certain api functions, such as Registerclass, you can tell the system to access this table. Of course, there are also local atomic tables and global atomic tables, which applications can access directly.

        The MAKEINTATOM macro (located in winbase.h) converts the specified atom into the corresponding string address so that it can be passed to functions that accept atoms or strings. This macro is used when calling CreateWindowEx.

Get drawing context 

        The code added later is from

HDC hDC = GetDC(m_handle); 

This line of code starts with

assert(m_context); 

This line of code ends. Its core purpose is to obtain the drawing context, which is m_context. The code used to get the context code is:

m_context = wglCreateContextAttribsARB(hDC, 0, openAttributes);

        Generally speaking, it is a confusing process for me, but I need to get the formal parameters first. The first one is hDC, which is the only one that needs to be explained. There is a lot of content, and I was very confused after searching for a long time. I need to understand it first. Whatever counts.

        HDC - Handle of the Device Context, Chinese means "device context handle", this thing will be used many times later. HDC is a Windows data structure that contains information about the drawing properties of a device, such as a monitor or printer . All draw calls are made through device context objects, which encapsulate the Windows API for drawing lines, shapes, and text.

        Although the GetDC function easily obtains this hDC, it sets its pixel format. That's the code below:

SetPixelFormat(hDC, iPixelFormat, &pixelFormatDesc);

        There are several functions designed in this process, but I basically don’t understand them, so I guess I can just write them down.

        Let's talk about a wglChoosePixelFormatARB function, which can find the appropriate pixel format.

        To set multisampling , you must use the wglChoosePixelFormatARB function. The key to using this function correctly is to create a temporary form. Through this form, we can obtain the necessary basic pixel format, and then use the wglChoosePixelFormatARB function to obtain the available multi-sampling pixel format. Finally, set the rendering window This pixel format is enough.

4. OGraphicsEngine class

        The OGraphicsEngine class has been added, and the code will be posted first:

        OGraphicsEngine.h file:

#pragma once
#include<OGL3D/Math/OVec4.h>

class OGraphicsEngine
{
public:
	OGraphicsEngine();
	~OGraphicsEngine();
public:
	void clear(const OVec4& color);
};

        OGraphicsEngine.cpp file:

#include<OGL3D/Graphics/OGraphicsEngine.h>
#include<glad/glad.h>
#include<glad/glad_wgl.h>
#include<assert.h>
#include<stdexcept>

OGraphicsEngine::OGraphicsEngine()
{
	WNDCLASSEX wc = {};
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpszClassName = L"OGL3DDummyWindow";
	wc.lpfnWndProc = DefWindowProc;
	wc.style = CS_OWNDC;

	auto classId = RegisterClassEx(&wc);
	assert(classId);

	auto dummyWindow = CreateWindowEx(NULL, MAKEINTATOM(classId), L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);

	assert(dummyWindow);

	HDC dummyDC = GetDC(dummyWindow);

	PIXELFORMATDESCRIPTOR pixelFormatDesc = {};
	pixelFormatDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pixelFormatDesc.nVersion = 1;
	pixelFormatDesc.iPixelType = PFD_TYPE_RGBA;
	pixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pixelFormatDesc.cColorBits = 32;
	pixelFormatDesc.cAlphaBits = 8;
	pixelFormatDesc.cDepthBits = 24;
	pixelFormatDesc.cStencilBits = 8;
	pixelFormatDesc.iLayerType = PFD_MAIN_PLANE;

	auto iPixelFormat = ChoosePixelFormat(dummyDC, &pixelFormatDesc);
	SetPixelFormat(dummyDC, iPixelFormat, &pixelFormatDesc);

	auto dummyContext = wglCreateContext(dummyDC);
	assert(dummyContext);

	wglMakeCurrent(dummyDC, dummyContext);

	if (!gladLoadWGL(dummyDC))throw std::runtime_error("OGraphic Engine Error:gladLoadWGL failed.");
	if (!gladLoadGL())throw std::runtime_error("OGraphic Engine Error:gladLoadGL failed.");

	wglMakeCurrent(dummyDC, 0);
	wglDeleteContext(dummyContext);
	ReleaseDC(dummyWindow, dummyDC);
	DestroyWindow(dummyWindow);
}

OGraphicsEngine::~OGraphicsEngine()
{
}

void OGraphicsEngine::clear(const OVec4& color)
{
	glClearColor(color.x,color.y,color.z,color.w);
	glClear(GL_COLOR_BUFFER_BIT);
}

        ​choosePixelFormat selects the most matching content from HDC and returns an index (iPixelFormat, the i at the beginning should be index).

        The SetPixelformat function sets the pixel format of the specified device context (HDC) to the format specified by the index (iPixelFormat).

        This operation means that the PIXELFORMATDESCRIPTOR you defined at the beginning is just your own wishful thinking. The device (monitor or printer) may support it (which is of course the best), or it may not support it. Once it does not support it, we will find the one closest to you. The required parameters are acceptable.

        The wglCreateContext (why does it start with wgl? It should be a GL function derived from the Wingdi.h file, and wingdi should be the Window Graphic Device Interface) function creates a rendering context that is suitable for   drawing on the device referenced by  hdc .

        Its return value is the HGLRC type (the Handle of GL Rendering Context, GL rendering context handle)

        wglMakeCurrent indicates that subsequent drawing work on hdc is based on dummyContext.

        I went around in a big circle just for these two sentences:

	if (!gladLoadWGL(dummyDC))throw std::runtime_error("OGraphic Engine Error:gladLoadWGL failed.");
	if (!gladLoadGL())throw std::runtime_error("OGraphic Engine Error:gladLoadGL failed.");

        Let me start by saying that throwing an exception through throw will terminate the thread.

        These two sentences also set the hDC status.

5. Supplement

        Add two files, one is the OVec4.h file and the other is the OPerequisites.h file.

        OVec4.h file:

#pragma once
#include<OGL3D/OPerequisites.h>

class OVec4
{
public:
	OVec4() {}
	OVec4(f32 x, f32 y, f32 z, f32 w) :x(x), y(y), z(z), w(w) {}
	~OVec4() {}

public:
	f32 x = 0, y = 0, z = 0, w = 0;
};

        OPerequisites.h file:

#pragma once

typedef float f32;

6. Postscript

        To be honest, Window programming is really convoluted and obscure (actually, I wanted to say it was **, but I want to maintain an elegant bottom line). Maybe this is the result of monopoly. In fact, when I was developing Apple Apps in the past, I felt that Apple was very unfriendly to developers (but very friendly to gamers and users!). I couldn’t help but recall that programming in C# in the Unity environment seemed really wonderful (Long Live Unity!). But having said that, the salary for developing game engines is usually higher than using the engine to make games. It can be regarded as compensation for being tortured by this kind of code :D       

Guess you like

Origin blog.csdn.net/ttod/article/details/135437815