Hands-on C++ Game Animation Programming阅读笔记(一)

Chapter 1: Creating a Game Window

这节课主要是创建Windows窗口,设置OpenGL的Context,具体分为:

  1. Open a Win32 window
  2. Create and bind an OpenGL 3.3 Core context
  3. Use glad to load OpenGL 3.3 Core functions
  4. Enable vsynch for the created window
  5. Understand the downloadable samples for this book

Creating the application class

先创建一个C++的空项目,然后就可以开始设置项目的Entry Point了,如果直接维护一个窗口Entry函数,那么这个函数肯定会非常大,内容非常多。这里选择了更好的做法:用一个抽象的Application类来设定相关的接口,代码如下:

class Application
{
public:
	virtual ~Application() {};

	// All these functions will be called directly from the Win32 window code
	virtual void Init() {};
	// inDeltaTime用于计算当前帧的时间, 等于lastFrameTime加上inDeltaTime
	virtual void Update(float inDeltaTime) {};
	// inAspectRatio: the aspect ratio of the window
	virtual void Render(float inAspectRatio) {};
	virtual void Shutdown() {};
};

Adding an OpenGL loader

OpenGL loader用于帮助寻找OpenGL的函数在内存里的函数指针,为了不一一人为去为函数寻址,可以使用Glad库,跑到书里提供的官方库,进去下载这三个文件,放到项目里面:
Hands-on C++ Game Animation Programming阅读笔记(一)_第1张图片


Creating a window

为了方便Debug,这里会调用WIN32的API,在Debug模式下创建两个窗口,一个用于正式的Display场景,一个作为Console控制台打印Log消息,此时会在main函数里额外调用一个WinMain函数,作为Debug的窗口。

第一步,先创建一个WinMain.cpp,然后加入基本的头文件:

// creates #define constants that reduce the amount of code that is brought in by including :
#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN

#include "glad.h"
#include 
#include 
#include "Application.h"

然后声明两个Win32函数:

// 只是声明函数, 暂时没给函数定义
// The window entry function
int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, int)
// The window event processing function
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

接下来设置Debug模式下的main函数:

#if _DEBUG
// 如果在Debug状态下, 把subsystem设置到console, 表示当前Linker是console模式
// 能在开启Main窗口的同时, 在main函数里调用WinMain函数, 再开启一个Win32的窗口, 用于Debug
#pragma comment(linker, "/subsystem:console")
int main(int argc, const char** argv)
{
	return WinMain(GetModuleHandle(NULL),
		NULL, GetCommandLineA(), SW_SHOWDEFAULT);
}
#else
#pragma comment(linker, "/subsystem:windows")
#endif

接着引入OpenGL的库文件,这里是用代码引入的,不是通过项目窗口属性来的,代码如下:

#pragma comment(lib, "opengl32.lib")

引入完库文件,就可以开始创建OpenGL的context了,windows上创建OpenGL的Context的函数是wglCreateContextAttribsARB,但此时没有该函数的地址(as it’s an extension function),需要通过wglGetProcAddress来获取。

为了获取函数的地址,首先要获得函数的签名,该函数签名在wglext.h文件内,但这里没必要把整个wglext.h文件include进来,所以这里只取了里面要用到的创建Context的内容,把它放到代码里:

// 用region把它们包含起来
#pragma region From <wglext.h>
	// 这些常量是在wglext.h头文件里, 对后续创建OpenGL 3.3 Core context有帮助的
	// 所以这里也单独拿了出来
	#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 
	#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 
	#define WGL_CONTEXT_FLAGS_ARB 0x2094 
	#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 
	#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 
	
	// 代表了wglCreateContextAttribsARB的函数签名, 这里有#define WINAPI      __stdcall
	typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC, HGLRC, const int*);
#pragma endregion

这里的:

typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC, HGLRC, const int*);

PFNWGLCREATECONTEXTATTRIBSARBPROC其实就是表示一个函数指针的类型,参数为(HDC, HGLRC, const int*),返回一个HGLRCHGLRC表示指针类型(更具体的可以看最后面的附录)。

除了创建OpenGL Context的函数需要取得以外,还有其他的wgl的函数也要拿出来,一个是vsynch相关的设置函数:wglSwapIntervalEXT函数,另外还需要两个功能性的支持函数:wglGetSwapIntervalEXTwglGetExtensionStringEXT,这三个函数在另外的wgl.h头文件里,相关代码也会被提取出来:

// 定义这三个函数的函数签名
typedef const char* (WINAPI* PFNWGLGETEXTENSIONSSTRINGEXTPROC) (void); 
typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC) (int); 
typedef int (WINAPI* PFNWGLGETSWAPINTERVALEXTPROC) (void);

创建全局变量

这里为了方便代码执行,创建了两个全局变量:

// 唯一的gApplication指针, 所指的对象代表唯一正在Running的application
Application* gApplication = 0;
// OpenGL里的VAO的id, 一般OpenGL里每一个对象会对应一个VAO, 前期这里的例子只需要一个VAO
GLuint gVertexArrayObject = 0;

至此以后,书里面说,再也不会有多的全局变量了,因为全局变量的存在make the program state harder to track,之所以有这两个,是为了在ShutDown Application以后还记录Application的引用(为了啥,释放内存?)

Throughout the rest of this book, there will be no other global variables. Global variables can make the program state harder to track. The reason these two exist is to easily reference them when the application is shutting down later. Next, you will start
implementing the WinMain function to open a new window


打开窗口

前面声明了WinMain函数,现在是时候实现它了,这个函数将会:

  1. 创建window class
  2. 登记registering the window class
  3. open a new window

先写入这些代码:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	// 第一件事, 当然是创建代表Application的全局对象了
	gApplication = new Application();

	// 第二件事, 就是创建WNDCLASSEX实例, 它代表了要创建的window的信息
	WNDCLASSEX wndclass;
	wndclass.cbSize = sizeof(WNDCLASSEX);// cb代表count bytes

	// 用flag表示类的style, wdnclass的flag是0011, 当window的水平或竖直方向size改变时
	// 使用Redraw重新绘制窗口
	wndclass.style = CS_HREDRAW | CS_VREDRAW;

	// lpfnWndProc: Long Pointer to the Windows Procedure function. 是一个函数指针
	// WndProc是前面声明的, 表示wndclass存储了一个调用系统函数的函数指针(好像目前是空的?)
	wndclass.lpfnWndProc = WndProc;

	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;

	// h代表handle, 这里的Instance指的是整个Application的实例, 从WinMain里传进来的
	wndclass.hInstance = hInstance;

	// hIcon is a handle to the application icon. 
	// 本质上应该是个指向结构体的指针, 这个结构体只有一个int作为public成员
	// 我发现这里的Handle都是这么包装的, 本质就是一个int
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	
	wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	
	// hCursor is a handle to the mouse cursor.
	// hCursor的类型其实就是HICON
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	
	// hbrBackground is a handle to the brush which will be used to fill the window background. 
	// We used pre-defined window color to paint background of our window.
	// h代表handle, br代表brush, 代表用于填充Window背景的brush, 好像就是窗口的背景颜色
	// COLOR_BTNFACE代表一种颜色类型
	wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
	
	// 代表menu名, 暂时不知道什么是menu
	wndclass.lpszMenuName = 0;
	
	// 代表类名, lpszClassName是个指向const string的指针
	wndclass.lpszClassName = "Win32 Game Window";

	// Win32的窗口登记
	RegisterClassEx(&wndclass);
}

有了窗口设置信息,接下来可以设置窗口的大小:

// 获取屏幕尺寸, 我这个电脑返回的是1920*1080
int screenWidth = GetSystemMetrics(SM_CXSCREEN);//CX
int screenHeight = GetSystemMetrics(SM_CYSCREEN);//CY

int clientWidth = 800;
int clientHeight = 600;
RECT windowRect;
// 四个值代表离屏幕四个方向上的间距, 顺序是左上右下, 这样写是为了让窗口在屏幕正中心
SetRect(&windowRect, (screenWidth / 2) - (clientWidth / 2),
		(screenHeight / 2) - (clientHeight / 2),
		(screenWidth / 2) + (clientWidth / 2),
		(screenHeight / 2) + (clientHeight / 2));

// 调整Window的Rect
AdjustWindowRectEx(&windowRect, wndclass.style, FALSE, 0);

// WS的意思是Window Style, 这些Flag都是一些窗口UI的设置, 后面有关于WS的详细介绍
// WS_MINIMIZEBOX: 窗口有最小化的按钮
// WS_SYSMENU: The window has a window menu on its title bar
DWORD style = (WS_OVERLAPPED | WS_CAPTION |
	WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
// | WS_THICKFRAME to resize

有了WNDCLASSEX和RECT两个描述窗口信息的对象,再加上WinMain里传入的Application的Handle,就可以创建窗口了:

// 根据WindowInfo和rect创建真正的Window
HWND hwnd = CreateWindowEx(0, wndclass.lpszClassName, "Game Window", wndclass.style, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, NULL, NULL, hInstance, szCmdLine);
HDC hdc = GetDC(hwnd);// DC应该是Device吧


创建OpenGL Context

窗口创建完以后,就是创建OpenGL Context了,这里要用wglCreateContextAttribsARB函数来创建Context,这个函数得用wglGetProcAddress手动获取函数指针地址:

// 创建pixel format descriptor, 主要是进行OpenGL Context的相关设置
PIXELFORMATDESCRIPTOR pfd;
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 32;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
// 这个函数会尝试去在输入的device context里寻找, 为了找到与输入的pfd最匹配的pixel format
int pixelFormat = ChoosePixelFormat(hdc, &pfd);
// 设置device context的pfd
SetPixelFormat(hdc, pixelFormat, &pfd);

// 根据hdc, 创建一个临时的OpenGL context using wglCreateContext
HGLRC tempRC = wglCreateContext(hdc);// 创建Render Context
wglMakeCurrent(hdc, tempRC);// 绑定到device上
// PFNWGLCREATECONTEXTATTRIBSARBPROC代表了wglCreateContextAttribsARB的函数签名
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;// 创建一个空的函数指针
// 通过wglGetProcAddress对函数寻址, 然后赋值
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateCon textAttribsARB");

此时创建了一个临时的Render Context,然后还获取了wglCreateContextAttribsARB的函数指针,接下来就可以调用这个函数了

const int attribList[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
	WGL_CONTEXT_MINOR_VERSION_ARB, 3,
	WGL_CONTEXT_FLAGS_ARB, 0,
	WGL_CONTEXT_PROFILE_MASK_ARB,
	WGL_CONTEXT_CORE_PROFILE_BIT_ARB,0, };
// 调用获取来的函数指针
HGLRC hglrc = wglCreateContextAttribsARB(hdc, 0, attribList);
// 重新绑定到Null的Render Context上
wglMakeCurrent(NULL, NULL);
// 删除遗留的临时Render Context
// 问题, 这里创建一个临时的RC, 然后创建真正的RC之后, 又删掉这个临时的RC, 这是何必?
wglDeleteContext(tempRC);
// 绑定到新创建的RenderContext上
wglMakeCurrent(hdc, hglrc);

使用Glad寻址所有OpenGL里的核心函数

if (!gladLoadGL()) 
	std::cout << "Could not initialize GLAD\n";
else 
	std::cout << "OpenGL Version " << GLVersion.major << "." << GLVersion.minor << "\n";

使用核心函数初始化OpenGL Context

在使用Glad加载完核心函数之后,就可以来初始化Render Context了,主要有:vsync、swapbuffer:

设置vsync
vsync的设置函数,其实名字并不叫vsync,我之前写过一部分关于它的内容,参考这里,回顾一下,OpenGL里的vsync是这么写的:

// This function sets the swap interval for the current OpenGL or OpenGL ES context
// i.e. the number of screen updates to wait from the time glfwSwapBuffers was called 
// before swapping the buffers and returning.
// This is sometimes called vertical synchronization, 
// vertical retrace synchronization or just vsync.
// [in]	interval:The minimum number of screen updates to wait for until the buffers are swapped by glfwSwapBuffers.
void glfwSwapInterval(int interval)

// 也就是说, 这里的interval代表帧数, 当它为1时, 代表GPU绘制愿意延时, 从而跟显示器保持一致的刷新频率

void SetVsync(bool enabled)
{
	if(enabled)
		glfwSwapInterval(1);
	else
		glfwSwapInterval(0);
}

先来设置vsync,它并不是win32的内置函数,而是一个拓展函数,需要查询wglGetExtensionStringEXT函数,代码如下:

// 设置vsync
// 对应的设置函数不属于核心函数, 所以要去寻址, 名字是wglGetExtensionsStringEXT
PFNWGLGETEXTENSIONSSTRINGEXTPROC _wglGetExtensionsStringEXT =
	(PFNWGLGETEXTENSIONSSTRINGEXTPROC)wglGetProcAddress("wglGetExtensionsStringEXT");
// 这个函数会返回一个string, 这里通过是否以"WGL_EXT_swap_control"结尾来判断是否支持vsync
// strstr: Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
// 会返回右边的字符串在左边字符串的位置的指针
bool swapControlSupported = strstr(_wglGetExtensionsStringEXT(), "WGL_EXT_swap_control") != 0;

int vsynch = 0;
if (swapControlSupported) 
{
	// 获取两个函数的地址: wglSwapIntervalEXT和wglGetSwapIntervalEXT
	PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT =
		(PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
	PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT =
		(PFNWGLGETSWAPINTERVALEXTPROC)wglGetProcAddress("wglGetSwapIntervalEXT");

	// 开启Vsync
	if(wglSwapIntervalEXT(1))
	{
		std::cout <<"Enabled vsynch\n";
		vsynch = wglGetSwapIntervalEXT();
	}
	else
		std::cout << "Could not enable vsynch\n";
}
else 
{
	//!swapControlSupported
	std::cout << "WGL_EXT_swap_control not supported\n";
}

创建VAO
VAO的指针是全局变量,这里直接绑定就行了,跟OpenGL写法一样:

glGenVertexArrays(1, &gVertexArrayObject);
glBindVertexArray(gVertexArrayObject);

绘制窗口
终于到了绘制窗口和Loop的地方了,代码如下:

	...
	ShowWindow(hwnd, SW_SHOW);
	UpdateWindow(hwnd);
	gApplication->Init();

	DWORD lastTick = GetTickCount();
	MSG msg;
	while (true)
	{
		// use the PeekMessage function to examine a message queue during a lengthy operation
		// 检查消息队列, 获取外部输入的消息, 看看是不是要关闭窗口
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			// 如果找到退出窗口的消息, 则退出循环, WM应该是Window Message的意思
			if (msg.message == WM_QUIT)
				break;

			// TranslateMessage produces WM_CHAR messages only for keys that 
			// are mapped to ASCII characters by the keyboard driver.
			// 如果这个消息不是退出的消息, 则会把这个消息, 转化为键盘驱动上的Ascii字符, 总之就是翻译一下
			TranslateMessage(&msg);
			// Dispatches a message to a window procedure
			// 分配到对应的执行程序上, 应该就是现在这个程序, 然后下一帧又会判断这个消息, 是不是退出的msg
			DispatchMessage(&msg);
		}

		// 算DeltaTime, 执行Update函数
		DWORD thisTick = GetTickCount();
		float dt = float(thisTick - lastTick) * 0.001f;
		lastTick = thisTick;
		if (gApplication != 0)
		{
			gApplication->Update(dt);
		}

		// 调用绘制的东西
		if (gApplication != 0)
		{
			RECT clientRect;
			GetClientRect(hwnd, &clientRect);
			clientWidth = clientRect.right - clientRect.left;
			clientHeight = clientRect.bottom - clientRect.top;
			glViewport(0, 0, clientWidth, clientHeight);
			glEnable(GL_DEPTH_TEST);// 讲道理, 这些东西可以应该放到Loop外
			glEnable(GL_CULL_FACE);
			glPointSize(5.0f);
			glBindVertexArray(gVertexArrayObject);
			glClearColor(0.5f, 0.6f, 0.7f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
			float aspect = (float)clientWidth / (float)clientHeight;
			gApplication->Render(aspect);
		}

		// 最后调用SwapBuffers
		if (gApplication != 0)
		{
			SwapBuffers(hdc);
			if (vsynch != 0)
				glFinish();
		}
	}// End Game Loop

	// Clear
	if (gApplication != 0)
	{
		std::cout << "Expectedapplication to be null on exit\n";
		delete gApplication;
	}

	return (int)msg.wParam;
}

WndProc函数的实现

之前在WinMain的前面声明了一个全局的函数,但是没有实现:

// 使用它来处理window messages
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// 这个函数会在WinMain在创建窗口信息对象时被使用
WNDCLASSEX wndclass;
...
// lpfnWndProc: Long Pointer to the Windows Procedure function. 是一个函数指针
// WndProc是前面声明的, 表示wndclass存储了一个调用系统函数的函数指针(好像目前是空的?)
wndclass.lpfnWndProc = WndProc;

这里的WndProc函数,就是用于处理窗口的Event的函数(the event processing function),比如Resize窗口事件,目前写的WndProc函数比较简单,主要需要处理窗口的关闭事件就可以了,函数实现直接放到最后面,代码如下:

LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	switch (iMsg)
	{
	case WM_CLOSE:
		// 关闭窗口时, 释放Application, 摧毁窗口
		if (gApplication != 0)
		{
			gApplication->Shutdown();
			gApplication = 0;
			DestroyWindow(hwnd);// emit a destroy window message
		}
		else
			std::cout << "Already shut down!\n";
		break;
	case WM_DESTROY:
		// 上面发出的DestroyWindow的msg应该会走到这里, 用于摧毁OpenGL Context和释放VAO对象
		if (gVertexArrayObject != 0)
		{
			HDC hdc = GetDC(hwnd);
			HGLRC hglrc = wglGetCurrentContext();

			glBindVertexArray(0);
			glDeleteVertexArrays(1, &gVertexArrayObject);
			gVertexArrayObject = 0;

			wglMakeCurrent(NULL, NULL);
			wglDeleteContext(hglrc);
			ReleaseDC(hwnd, hdc);

			PostQuitMessage(0);
		}
		else
			std::cout << "Got multiple destroy messages\n";
		break;
	// The paint and erase background messages are safe to ignore since OpenGL is managing rendering to the window
	case WM_PAINT:
	case WM_ERASEBKGND:
		return 0;
	}

	// If the message received isn't one of the messages already handled, forward it to the default window message function :
	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

大功告成

这么复杂的一番操作,终于看到窗口了(不过我还是很疑惑为什么OpenGL要搞一套通用的API,又还有个Win32的OpenGL api,remain吧),如下图所示:
Hands-on C++ Game Animation Programming阅读笔记(一)_第2张图片

顺便提一下,Github上有相关的代码可以看,上面还提供了一个叫AllChapters的工程,里面使用Nuklear (https://github.com/vurtun/nuklear)来展示UI,The part of the UI that is displayed is a stats counter in the upper-right corner of the screen. It looks like this:
Hands-on C++ Game Animation Programming阅读笔记(一)_第3张图片

第一部分是通用信息,Display frequency是fps;第二部分包含了high-level frame timings,应该是为了查奇怪的帧的,The time displayed will turn red if there was a stale(不新鲜的,腐坏的) frame in the last 60 frames. Some stale frames are unavoidable;如果frame rate降到59.9,文本会变红,维持一秒钟,偶尔看到一次还没啥,但是长期为红色就说明有问题了;第三部分是GPU的Timer,表示GPU运行速度,用于调试heavy draw call;最后一个部分是CPU的Timer,which are helpful for figuring out which phase of the problem has a bottleneck.

还有一点,此书使用的是C++ stl容器,这种标准库在Debug下,由于要做error检查,会有一点慢,所以性能测试应该在release模式下做。


附录

什么是ARB

参考资料:what-does-arb-mean-in-the-opengl-functions

ARB全称是Architecture Review Board(架构审查委员会),把它理解为一个官方版本名字就行了:

The OpenGL Architecture Review Board (ARB), was an independent consortium(集团) formed in 1992, that governed the future of OpenGL, proposing and approving changes to the specification, new releases, and conformance testing.


关于Handle

其实可以看到,很多带H开头的类,本质上就是一个struct,里面只有一个int数据成员,他被作为handle。
参考:https://digestingduck.blogspot.com/2010/03/save-games-and-all-that.html

Handle是一种简略的方式,它用于保存追踪对象的引用,指针与Handle的区别就是,handles可以是无效的(invalidated),一个无效的指针()野指针会非常恐怖,但是一个无效的Handle没什么大不了的,有点类似GUID,无非是找不到对应的对象而已。

Handles are simple abstraction which allows to keep track references to your data. The difference to a pointer is that handles can be invalidated, and you don’t need to worry about someone having a reference to your data. The next time the data is accessed, the access will just fail, in which case the user should also invalidate the handle.

有的系统是基于指针驱动的,也有的系统是基于Handle驱动的,通常基于指针的系统,会有一个回调系统,用于负责在对象销毁时,摧毁对应的指针,不过也可以使用智能指针或者引用计数

还有一个,Handle可以用于数据保存,指针的地址是会一直变化的,而Handle不会


PFNWGLCREATECONTEXTATTRIBSARBPROC

HGLRC有点不明白什么意思,它的意思是handle to GL Rendering Context),这里具体把宏展开来看看:

// 在我电脑的平台下, 有:
#define WINAPI      __stdcall

// 关于HGLRC和HDC的宏定义
DECLARE_HANDLE(HGLRC);
DECLARE_HANDLE(HDC);
DECLARE_HANDLE(name) struct name##__{int unused;}; typedef struct name##__ *name

所以这句话实际上等于:

// 声明一个struct, 名字为HGLRC__
struct HGLRC__{int unused;}
// HGLRC是一个指针类型, 即HGLRC__类的指针
typedef struct HGLRC__ *HGLRC

// 声明一个struct, 名字为HDC__
struct HDC__{int unused;}
// HDC是一个指针类型, 即HDC__类的指针
typedef struct HDC__ *HDC
// PFNWGLCREATECONTEXTATTRIBSARBPROC是这次typedef的类型的别名, 只是一个名字, 有点长而已
typedef HGLRC__ *(__stdcall* PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC__*, HGLRC__*, const int*);

所以PFNWGLCREATECONTEXTATTRIBSARBPROC是一个函数指针,参数为(HDC__*, HGLRC__*, const int*),返回一个HGLRC__类型的指针

Win32 API的黑话整理

WindowClassEx相关的参数基本都可以在这里找到:https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-wndclassexa?redirectedfrom=MSDN
https://codesteps.com/2014/07/18/win32-programming-how-to-create-a-simple-gui-graphical-user-interface-based-application-part-2/


Win32 API里的ex符号

参考:https://stackoverflow.com/questions/4039054/what-does-ex-stand-for-in-windows-api-function-names
Win32 API里函数或者类名后面经常会有EX字母,这里的EX可以理解为Extension,或者升级版

When Microsoft updates a function and the new function is incompatible with the old one, Microsoft continues to support the old function. The new function keeps the same name as the old function, with added -Ex suffix.
When Microsoft updates a function and the new function is incompatible with the old one, Microsoft continues to support the old function. The new function keeps the same name as the old function, with added -Ex suffix.

Win32 API里的cb

参考:https://stackoverflow.com/questions/18298425/in-win32-what-does-the-size-member-cb-name-actually-mean
cb的意思大概是count bytes,也就是字节数,比如:

// 2. 填充WNDCLASSEX类的实例, 这个示例用于存储创建的window的相关信息
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(WNDCLASSEX);

WindowClass的lpfnWndProc、hInstance和lpszClassName

lpfnWndProc全称: Long Pointer to the Windows Procedure function. 是一个函数指针,这个函数一般叫做window procedure or “window proc.”,procedure定义了窗口的绝大多数行为,
hInstance的h代表handle,Instance指的是application实例,可以从WinMain的参数里获取
lpszClassName类型为LPCTSTR,LPCSTR is a pointer to a const string,代表指向const string的指针,CT代表const
甚至还有别的类似的指针常量的类型,参考:https://stackoverflow.com/questions/321413/lpcstr-lpctstr-and-lptstr。这里其实就是代表Window的指针,后面可以接一个string


cbClsExtra和cbWndExtra

参考:https://stackoverflow.com/questions/13330225/cbclsextra-and-cbwndextra
https://codesteps.com/2014/07/18/win32-programming-how-to-create-a-simple-gui-graphical-user-interface-based-application-part-2/
cbClsExtra类型为int,cb代表count of bytes,这里的CLS好像指的是Common Language Specification

这两个东西,都是用于在创建窗口实例时额外分配内存用的,像我这里目前用不到,所以写为:

wc.cbClsExtra = 0;
wc.cbWndExtra = 0;

关于WNDCLASSEX类

Win32的API有两种数据结构,可以用于表示Window的相关信息,就是WNDCLASSEXWNDCLASS,前者的版本更新一些


CS_HREDRAW | CS_VREDRAW

https://stackoverflow.com/questions/24296750/cs-hredraw-cs-vredraw-what-does-it-do
主要是对于这段代码的理解:

wndclass.style = CS_HREDRAW | CS_VREDRAW;

这里的CS_HREDRAW应该划分为:CS、H和Redraw,H代表Horizontal,而后面的V代表Vertical,这行代码表示,当这个窗口水平或者竖直方向上的size改变时,就会重新绘制该窗口



Atom

atom本意是原子的,提出几个概念性的问题:

  • Win32API里,什么是Atom Tables?
  • Win32 API里的Atom和C++语言里的是不是都是多线程里原子操作的意思?

参考:https://stackoverflow.com/questions/10525511/what-is-the-atom-data-type
https://docs.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables


About Atom Tables

An atom table is a system-defined table that stores strings and corresponding identifiers. An application places a string in an atom table and receives a 16-bit integer, called an atom, that can be used to access the string. A string that has been placed in an atom table is called an atom name.

Atom table是系统定义的表,它的key是string,value是一个16位的整型,这个整数就叫做atom,string叫做atom name。

Win32系统会定义很多个这样的表,作用各不相同,比如说 Dynamic Data Exchange (DDE) applications会使用global atom table,用于和其他的应用共享item-name and topic-name strings,不同的应用通过atoms来交互

Rather than passing actual strings, a DDE application passes global atoms to its partner application. The partner uses the atoms to obtain the strings from the atom table.

ATOM is a 16-bit Windows handle-like primitive. It’s value is completely opaque to user-mode. It is not a pointer or an index.
typedef unsigned short ATOM;

其实,Win32里的ATOM是一个类似于Handle的东西,本质上是16位的整数,用于kernel内部使用。


Window Style

参考:https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles

WS_BORDER:The window has a thin-line border.
WS_CAPTION:The window has a title bar (includes the WS_BORDER style). 窗口有一个标题条,还有很细的线代表边界
WS_OVERLAPPED: The window is an overlapped window. An overlapped window has a title bar and a border. Same as the WS_TILED style.

另外有这个,好像就是我写的flag的总和:
在这里插入图片描述


TranslateMessage和DispatchMessage函数

在窗口的MainLoop,有这么一段代码:

while (true)
{
	// use the PeekMessage function to examine a message queue during a lengthy operation
	// 检查消息队列, 获取外部输入的消息
	if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		// 如果找到退出窗口的消息, 则退出循环, WM应该是Window Message的意思
		if (msg.message == WM_QUIT)
			break;

		// TranslateMessage produces WM_CHAR messages only for keys that 
		// are mapped to ASCII characters by the keyboard driver.
		// 如果这个消息不是退出的消息, 则会把这个消息, 转化为键盘驱动上的Ascii字符, 总之就是翻译一下
		TranslateMessage(&msg);
		// Dispatches a message to a window procedure
		// 分配到对应的执行程序上, 应该就是现在这个程序, 然后下一帧又会判断这个消息, 是不是退出的msg
		DispatchMessage(&msg);
	}

	...
}

或者这种类似的写法:

while (GetMessage(&message, NULL, 0, 0) > 0) {
  TranslateMessage(&message);
  DispatchMessage(&message);
}

这里的TranslateMessage起的是翻译作用,Translates virtual-key messages into character messages,翻译后得到的字符信息,会被传入调用这段代码的线程的消息队列里,它会等待下一次,这个线程去使用GetMessage或PeekMessage函数来读取,而DispatchMessage的作用是dispatcher,它会把对应的消息传递到窗口程序上(window procedure)去等待执行,

为什么OpenGL要搞一套通用的API,又还有个Win32的OpenGL api


Remain阅读

https://www.codeproject.com/Articles/22642/What-Every-Computer-Programmer-Should-Know-About-W
The relationship between Windows API, CRT and C++ Standard Library.

C. Petzold’s] Book “Windows-Programming”

你可能感兴趣的:(笔记,动画,Animation)