从零开始手敲次世代游戏引擎(廿)

上一篇我们完成了一个基本的内存管理模块。接下来让我们把我们前面图形支线任务的代码整合到我们的引擎代码里面。

在我们继续进行之前,首先让我们调整一下我们的CMakefileLists.txt,使得我们在Windows平台下缺省也使用类似Linux平台的编译方式(gmake + clang)

cmake_minimum_required (VERSION 3.1) 
set (CMAKE_C_COMPILER               "clang-cl")
set (CMAKE_C_FLAGS                  "-Wall")
set (CMAKE_C_FLAGS_DEBUG            "/Debug")
set (CMAKE_C_FLAGS_MINSIZEREL       "-Os -DNDEBUG")
set (CMAKE_C_FLAGS_RELEASE          "-O4 -DNDEBUG")
set (CMAKE_C_FLAGS_RELWITHDEBINFO   "-O2 /Debug")
set (CMAKE_C_STANDARD 11)
set (CMAKE_CXX_COMPILER             "clang-cl")
set (CMAKE_CXX_FLAGS                "-Wall -Xclang -std=gnu++14")
set (CMAKE_CXX_FLAGS_DEBUG          "/Debug")
set (CMAKE_CXX_FLAGS_MINSIZEREL     "-Os -DNDEBUG")
set (CMAKE_CXX_FLAGS_RELEASE        "-O4 -DNDEBUG")
set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 /Debug")
project (GameEngineFromScrath)
include_directories("${PROJECT_SOURCE_DIR}/Framework/Common")
include_directories("${PROJECT_SOURCE_DIR}/Framework/Interface")
add_subdirectory(Framework)
add_subdirectory(Empty

我们强行指定了C/C++编译器为”clang-cl”。”clang-cl”是clang的一个Wrapper,可以兼容Visual Studio编译器(cl.exe)的编译选项。这个我们在之前的文章当中也交代过了。

如果之前已经用CMake生成过Visual Studio项目文件,让我们清空我们的build文件夹,或者也可以新建一个build文件夹。

C:UsersTim.AzureADSourceReposGameEngineFromScratchbuild>rm -rf *

然后我们通过参数指定CMake生成Unix Makefile

C:UsersTim.AzureADSourceReposGameEngineFromScratchbuild>cmake -G "Unix Makefiles" ..

然后我们就可以使用make命令进行编译了。

C:UsersTim.AzureADSourceReposGameEngineFromScratchbuild>make
Scanning dependencies of target Common
[ 12%] Building CXX object Framework/Common/CMakeFiles/Common.dir/Allocator.cpp.obj
clang-cl.exe: warning: unknown argument ignored in clang-cl: '-std=gnu++11' [-Wunknown-argument]
C:/Users/Tim.AzureAD/Source/Repos/GameEngineFromScratch/Framework/Common/Allocator.cpp(13,9):  warning: field 'm_szBlockSize' will be initialized
      after field 'm_szAlignmentSize' [-Wreorder]
        m_szBlockSize(0), m_szAlignmentSize(0), m_nBlocksPerPage(0),
        ^
C:/Users/Tim.AzureAD/Source/Repos/GameEngineFromScratch/Framework/Common/Allocator.cpp(13,49):  warning: field 'm_nBlocksPerPage' will be
      initialized after field 'm_pPageList' [-Wreorder]
        m_szBlockSize(0), m_szAlignmentSize(0), m_nBlocksPerPage(0),
                                                ^
2 warnings generated.
[ 25%] Building CXX object Framework/Common/CMakeFiles/Common.dir/BaseApplication.cpp.obj
clang-cl.exe: warning: unknown argument ignored in clang-cl: '-std=gnu++11' [-Wunknown-argument]
[ 37%] Building CXX object Framework/Common/CMakeFiles/Common.dir/GraphicsManager.cpp.obj
clang-cl.exe: warning: unknown argument ignored in clang-cl: '-std=gnu++11' [-Wunknown-argument]
[ 50%] Building CXX object Framework/Common/CMakeFiles/Common.dir/MemoryManager.cpp.obj
clang-cl.exe: warning: unknown argument ignored in clang-cl: '-std=gnu++11' [-Wunknown-argument]
[ 62%] Building CXX object Framework/Common/CMakeFiles/Common.dir/main.cpp.obj
clang-cl.exe: warning: unknown argument ignored in clang-cl: '-std=gnu++11' [-Wunknown-argument]
[ 75%] Linking CXX static library Common.lib
[ 75%] Built target Common
Scanning dependencies of target Empty
[ 87%] Building CXX object Empty/CMakeFiles/Empty.dir/EmptyApplication.cpp.obj
clang-cl.exe: warning: unknown argument ignored in clang-cl: '-std=gnu++11' [-Wunknown-argument]
[100%] Linking CXX executable Empty.exe
[100%] Built target Empty

由于我们在CMakeLists.txt当中打开了”-wall”开关,我们看到了更多的warning。这些warning通常不会直接引起问题,但是对于多平台的code,这些warning常常是值得关注的。比如我们这里的关于初始化顺序方面,以及返回值方面的warning。让我们对其进行修正:

C:UsersTim.AzureADSourceReposGameEngineFromScratchbuild>git diff ..FrameworkCommonAllocator.cpp
diff --git a/Framework/Common/Allocator.cpp b/Framework/Common/Allocator.cpp
index 6526691..b0c5d30 100644
--- a/Framework/Common/Allocator.cpp
+++ b/Framework/Common/Allocator.cpp
@@ -9,9 +9,9 @@
 using namespace My;

 My::Allocator::Allocator()
-        : m_szDataSize(0), m_szPageSize(0),
-        m_szBlockSize(0), m_szAlignmentSize(0), m_nBlocksPerPage(0),
-        m_pPageList(nullptr), m_pFreeList(nullptr)
+        : m_pPageList(nullptr), m_pFreeList(nullptr),
+        m_szDataSize(0), m_szPageSize(0),
+        m_szAlignmentSize(0), m_szBlockSize(0), m_nBlocksPerPage(0)
 {
 }

另外,我们在文章(二)当中编译的clang工具链是32位的。这并不影响我们的使用,但是如果我们想要升级到64位,需要按照参考引用1当中的提示,重新编译我们的工具链。

By default, the Visual Studio project files generated by CMake use the 32-bit toolset. If you are developing on a 64-bit version of Windows and want to use the 64-bit toolset, pass the “-Thost=x64“ flag when generating the Visual Studio solution. This requires CMake 3.8.0 or later.

为了分别编译Debug版和Release版,我们需要创建两个Build目录。目录名字随便,我们这里使用build/Debug, build/Release。然后在build/Debug当中执行下面的命令:

cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug ....

在build/Release当中执行下面的命令

cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ....

就可以分别生成Debug和Release的makefile。然后在各自的目录当中执行make就可以了。

好了。如我们之前设计的,Application模块是用来抽象平台相关接口,并读取配置文件的。我们首先创建一个结构体,用来保存我们的配置:

#pragma once
#include <cstdint>
#include <iostream>

namespace My {
	struct GfxConfiguration {
		/// Inline all-elements constructor.
		/// param[in] _apiVer the API and version information
		/// param[in] r the red color depth in bits
		/// param[in] g the green color depth in bits
		/// param[in] b the blue color depth in bits
		/// param[in] a the alpha color depth in bits
		/// param[in] d the depth buffer depth in bits
		/// param[in] s the stencil buffer depth in bits
		/// param[in] msaa the msaa sample count
		/// param[in] width the screen width in pixel
		/// param[in] height the screen height in pixel
		GfxConfiguration(uint32_t r = 8, uint32_t g = 8,
			uint32_t b = 8, uint32_t a = 8,
			uint32_t d = 24, uint32_t s = 0, uint32_t msaa = 0,
			uint32_t width = 1920, uint32_t height = 1080) :
			redBits(r), greenBits(g), blueBits(b), alphaBits(a),
			depthBits(d), stencilBits(s), msaaSamples(msaa),
			screenWidth(width), screenHeight(height)
		{}

		uint32_t redBits; ///< red color channel depth in bits
		uint32_t greenBits; ///< green color channel depth in bits
		uint32_t blueBits; ///< blue color channel depth in bits
		uint32_t alphaBits; ///< alpha color channel depth in bits
		uint32_t depthBits; ///< depth buffer depth in bits
		uint32_t stencilBits; ///< stencil buffer depth in bits
		uint32_t msaaSamples; ///< MSAA samples
		uint32_t screenWidth;
		uint32_t screenHeight;

        friend std::ostream& operator<<(std::ostream& out, const GfxConfiguration& conf)
        { 
            out << "GfxConfiguration:" << 
                " R:"  << conf.redBits << 
                " G:"  << conf.greenBits <<
                " B:"  << conf.blueBits <<
                " A:"  << conf.alphaBits <<
                " D:"  << conf.depthBits <<
                " S:"  << conf.stencilBits <<
                " M:"  << conf.msaaSamples <<
                " W:"  << conf.screenWidth <<
                " H:"  << conf.screenHeight <<
                std::endl;
            return out;
        }
	};
}

然后我们修改我们的BaseApplication基类,添加一个成员变量用于保存这个配置,同时添加一个构造函数用于接受配置:

diff --git a/Framework/Common/BaseApplication.hpp b/Framework/Common/BaseApplication.hpp
index 3244a64..42bece6 100644
--- a/Framework/Common/BaseApplication.hpp
+++ b/Framework/Common/BaseApplication.hpp
@@ -1,10 +1,12 @@
 #pragma once
 #include "IApplication.hpp"
+#include "GfxConfiguration.h"

 namespace My {
        class BaseApplication : implements IApplication
        {
        public:
+        BaseApplication(GfxConfiguration& cfg);
                virtual int Initialize();
                virtual void Finalize();
                // One cycle of the main loop
@@ -15,6 +17,11 @@ namespace My {
        protected:
                // Flag if need quit the main loop of the application
                static bool m_bQuit;
+               GfxConfiguration m_Config;
+
+       private:
+               // hide the default construct to enforce a configuration
+               BaseApplication(){};
        };
 }
diff --git a/Framework/Common/BaseApplication.cpp b/Framework/Common/BaseApplication.cpp
index f61335e..a142115 100644
--- a/Framework/Common/BaseApplication.cpp
+++ b/Framework/Common/BaseApplication.cpp
@@ -1,9 +1,15 @@
 #include "BaseApplication.hpp"
+#include <iostream>
+
+bool My::BaseApplication::m_bQuit = false;
+
+My::BaseApplication::BaseApplication(GfxConfiguration& cfg)
+  :m_Config(cfg)
+{

+}

 // Parse command line, read configuration, initialize all sub modules
 int My::BaseApplication::Initialize()
 {
-       m_bQuit = false;
+    std::cout << m_Config;

        return 0;
 }

好,然后让我们编译执行这个程序。

C:UsersTim.AzureADSourceReposGameEngineFromScratchbuildDebug>make
Scanning dependencies of target Common
[ 12%] Building CXX object Framework/Common/CMakeFiles/Common.dir/Allocator.cpp.obj
[ 25%] Building CXX object Framework/Common/CMakeFiles/Common.dir/BaseApplication.cpp.obj
[ 37%] Building CXX object Framework/Common/CMakeFiles/Common.dir/GraphicsManager.cpp.obj
[ 50%] Building CXX object Framework/Common/CMakeFiles/Common.dir/MemoryManager.cpp.obj
[ 62%] Building CXX object Framework/Common/CMakeFiles/Common.dir/main.cpp.obj
[ 75%] Linking CXX static library Common.lib
[ 75%] Built target Common
Scanning dependencies of target Empty
[ 87%] Building CXX object Empty/CMakeFiles/Empty.dir/EmptyApplication.cpp.obj
[100%] Linking CXX executable Empty.exe
LINK : 没有找到 Empty.exe 或上一个增量链接没有生成它;正在执行完全链接
[100%] Built target Empty

C:UsersTim.AzureADSourceReposGameEngineFromScratchbuildDebug>EmptyEmpty.exe
GfxConfiguration: R:8 G:8 B:8 A:8 D:24 S:0 M:0 W:1920 H:1080

按Ctrl-C退出执行。

接下来我们统合文章(七)所写代码。我们在Platform/Windows之下新建两个文件,并从BaseApplication类派生出WindowsApplication类

#include <windows.h>
#include <windowsx.h>
#include "BaseApplication.hpp"

namespace My {
    class WindowsApplication : public BaseApplication
    {
    public:
        WindowsApplication(GfxConfiguration& config)
            : BaseApplication(config) {};

       	virtual int Initialize();
		virtual void Finalize();
		// One cycle of the main loop
		virtual void Tick();

        // the WindowProc function prototype
        static LRESULT CALLBACK WindowProc(HWND hWnd,
                         UINT message,
                         WPARAM wParam,
                         LPARAM lParam);
    };
}
#include "WindowsApplication.hpp"
#include <tchar.h>

using namespace My;


namespace My {
    GfxConfiguration config(8, 8, 8, 8, 32, 0, 0, 960, 540, L"Game Engine From Scratch (Windows)");
    WindowsApplication  g_App(config);
    IApplication*       g_pApp = &g_App;
}

int My::WindowsApplication::Initialize()
{
    int result;

    result = BaseApplication::Initialize();

    if (result != 0)
        exit(result);

    // get the HINSTANCE of the Console Program
    HINSTANCE hInstance = GetModuleHandle(NULL);

    // the handle for the window, filled by a function
    HWND hWnd;
    // this struct holds information for the window class
    WNDCLASSEX wc;

    // clear out the window class for use
    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    // fill in the struct with the needed information
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = _T("GameEngineFromScratch");

    // register the window class
    RegisterClassEx(&wc);

    // create the window and use the result as the handle
    hWnd = CreateWindowExW(0,
                          L"GameEngineFromScratch",      // name of the window class
                          m_Config.appName,             // title of the window
                          WS_OVERLAPPEDWINDOW,              // window style
                          CW_USEDEFAULT,                    // x-position of the window
                          CW_USEDEFAULT,                    // y-position of the window
                          m_Config.screenWidth,             // width of the window
                          m_Config.screenHeight,            // height of the window
                          NULL,                             // we have no parent window, NULL
                          NULL,                             // we aren't using menus, NULL
                          hInstance,                        // application handle
                          NULL);                            // used with multiple windows, NULL

    // display the window on the screen
    ShowWindow(hWnd, SW_SHOW);

    return result;
}

void My::WindowsApplication::Finalize()
{
}

void My::WindowsApplication::Tick()
{
    // this struct holds Windows event messages
    MSG msg;

    // we use PeekMessage instead of GetMessage here
    // because we should not block the thread at anywhere
    // except the engine execution driver module 
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        // translate keystroke messages into the right format
        TranslateMessage(&msg);

        // send the message to the WindowProc function
        DispatchMessage(&msg); 
    }
}

// this is the main message handler for the program
LRESULT CALLBACK My::WindowsApplication::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // sort through and find what code to run for the message given
    switch(message)
    {
	case WM_PAINT:
        // we will replace this part with Rendering Module
	    {
	    } break;

        // this message is read when the window is closed
    case WM_DESTROY:
        {
            // close the application entirely
            PostQuitMessage(0);
            BaseApplication::m_bQuit = true;
            return 0;
        }
    }

    // Handle any messages the switch statement didn't
    return DefWindowProc (hWnd, message, wParam, lParam);
}

注意我们的主入口(定义在Framework/Common/main.cpp)是main()而不是WinMain()。但是我们依然可以创建窗口。主要的区别是我们需要自己获取hinstance,具体请参看代码。

参考引用:

  1. Clang – Getting Started

本作品采用知识共享署名 4.0 国际许可协议进行许可。

留下评论