上一篇我们完成了一个基本的内存管理模块。接下来让我们把我们前面图形支线任务的代码整合到我们的引擎代码里面。
在我们继续进行之前,首先让我们调整一下我们的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,具体请参看代码。
参考引用:
本作品采用知识共享署名 4.0 国际许可协议进行许可。