从零开始手敲次世代游戏引擎(macOS特别篇)

双十一快到了,不知道大家今年准备剁手购买什么。作者一直心念着之前提出想要在MacOS上面学习本教程的同学,双十一剁手入了一个MacPro本,摸了2天来更新这篇文章。

(在此之前作者从来未使用过Mac。所以写的不对请不吝赐教)

根据资料(*1)显示,MacOS从版本10开始基于一个被称为XNU的内核(Darwin达尔文系统)。这个内核是基于BSD系统的。BSD是一种Unix系统,而Linux是模仿Unix系统开发的。所以我们之前写的Linux版本应该是可以比较容易地在MacOS上面编译的。是不是这样,有多容易,我们实际来试一下。

首先我们需要安装编译环境。之前我们说过,MacOSX之后标准是使用clang。但是这个环境缺省是没有安装到系统当中的。最为简单的安装方法是安装Xcode。我们可以在Apple Store里面方便的找到它并安装。

因为我们是要工作在命令行,安装完成之后,我们需要打开命令行窗口安装Xcode的命令行工具。在MacOS打开一个程序最快的方法是使用Spotlight搜索,我们按下()键和空格键,然后输入term,按下回车键。

然后输入

xcode-select --install

然后根据屏幕提示完成安装。

接下来是安装git/cmake等工具。在Mac上面安装GNU软件似乎一般有两个方式,一个是通过一个叫做HomeBrew的包管理,而另外一个则是使用MacPort。我首先是试了一下HomeBrew,这个好像是基于ruby的,里面可以用的包很少,比如找不到libxcb。所以后来我又换成了MacPort。MacPort就是Port的Mac版,而Port是BSD系统当中的包管理器,就相当于Linux系统当中的APT或者YUM工具。只不过Port是下载源代码到本地进行编译安装的,而不是直接下载二进制包进行安装。(所以我们需要先安装Xcode命令行工具)

MacPort的安装方法在MacPort的网站上有详细介绍。需要注意的就是必须根据Mac系统的版本下载对应的包。不同的版本之间是不兼容的。

装好MacPort之后,在命令行运行

sudo port selfupdate
sudo port install git git-lfs cmake

就可以完成相关工具的安装。

然后我们就可以尝试checkout我们的代码并进行编译了。首先我们尝试编译我们的第三方依赖库,crossguid。运行项目根目录下的build_crossguid.sh,直接编译成功了。

接下来我们尝试编译OpenGEX,运行项目根目录下的build_opengex.sh,也是一次成功。

然后我们编译我们的引擎。这里主要有几个问题:

  1. 首先是找不到ispc。这主要是由于我们CMake文件的写法导致的(CMAKE_SYSTEM_NAME变成了Darwin而不是Linux)。调整之后找到。可以直接使用Linux下编译的ispc的二进制文件,也可以在MacOS下面重新编译一个。编译的脚本在External/src/ispc下面(如果是空目录请在项目根目录执行
    git submodule update –init External/src/ispc
    获取源代码和编译脚本)只不过需要注意的是Xcode缺省安装的toolchain似乎并不包括llvm相关的库,而编译ispc需要这些库。我的做法是直接用项目根目录下的build_llvm_clang.sh脚本重新完整编译了一套llvm工具出来。应该也可以在Xcode里面安装这些库,只不过我没有去调查了;
  2. 提示找不到xcb、X11等库。这个问题又可以分两个方面:
  1. 没有X11 Server。MacOS是使用的自己的图形服务器(Quartz)和图形库(Cocoa)。在老的版本当中也同时提供了X11 Server,但是好像从OS X开始X11被剥离到一个名为XQuartz的项目当中,并在GitHub开源了(*2)。所以需要安装XQuartz。安装过程请参考GitHub相关项目页面;
  2. 没有xcb、X11等库。这个通过port安装就可以了。需要安装的包名称为:
    xorg-libxcb
    xorg-libX11

好了。进行了这些准备之后,代码就可以完成编译了(执行build.sh)。跑一下Test目录下的测试,大部分没有问题,但是GeomMathTest的部分结果出现问题。如果在cmake之后指定‘-G “Xcode”’,则最后的可执行文件的链接会出错,提示libGeomMath.a的符号表有问题。经过调试发现是我们生成libGeomMath.a的时候只是使用ar工具将obj文件压成了库,但是没有执行ranlib工具来更新库当中的符号表。如下修改Framework/Geommath/ispc/CMakeLists.txt之后问题得到解决:

add_custom_command(OUTPUT ${GEOMMATH_LIB_FILE}
         COMMAND ${CMAKE_AR} ${ISPC_LIBRARIAN_OPTIONS} ${OBJECTS}
+        COMMAND ${CMAKE_RANLIB} ${GEOMMATH_LIB_FILE}
         COMMAND rm -v ${OBJECTS}
         DEPENDS ${OBJECTS}
         )

最后的问题是编译虽然通过了,但是执行./build/Platform/Darwin/MyGameEngineOpenGL会直接发生段错误而强行关闭。如下:

chenwenlideMacBook-Pro:GameEngineFromScratchPrivate chenwenli$ ./build/Platform/Darwin/MyGameEngineOpenGL 
Segmentation fault: 11

使用lldb工具进行调试,发现错误信息如下:

Process 51269 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x0000000000000000
error: memory read failed for 0x0
Target 0: (MyGameEngineOpenGL) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x0000000000000000
    frame #1: 0x0000000100002a9e MyGameEngineOpenGL`My::OpenGLApplication::Initialize(this=0x0000000100701410) at OpenGLApplication.cpp:106
    frame #2: 0x0000000100043a29 MyGameEngineOpenGL`main(argc=1, argv=0x00007ffeefbffa48) at main.cpp:7
    frame #3: 0x00007fff6ad71145 libdyld.dylib`start + 1
    frame #4: 0x00007fff6ad71145 libdyld.dylib`start + 1

出错位置是在OpenGLApplication.cpp:106,使用l命令打出代码:

(lldb) l OpenGLApplication.cpp:106
   96  	    {
   97  	        fprintf(stderr, "Can't open displayn");
   98  	        return -1;
   99  	    }
   100 	
   101 	    default_screen = DefaultScreen(m_pDisplay);
   102 	
   103 	    gladLoadGLX(m_pDisplay, default_screen);
   104 	
   105 	    /* Query framebuffer configurations */
   106 	    fb_configs = glXChooseFBConfig(m_pDisplay, default_screen, visual_attribs, &num_fb_configs);

继续跟踪可以发现是相关的glX函数指针未被glad正确加载造成的。这个问题的解决方法现在还在摸索当中。。。(已经解决了,看更新2)

# 对了,代码在“mac”分支(branch)当中。

#更新1: 有进展了,从零开始手敲次世代游戏引擎(十二)当中的代码可以正常出图了:

在MacOS下编译的命令与Linux下基本相同,但是需要加入头文件和库的查找路径。

分两种情况:

  • 如果是直接安装的XQuartz的dmg的包,那么是下面这样:
chenwenlideMBP:GameEngineFromScratchPrivate chenwenli$ clang -I /usr/X11/include/ -L /usr/X11/lib -o helloengine_opengl helloengine_opengl.cpp -lxcb -lX11 -lX11-xcb -lGL -lGLU
  • 如果是通过macports安装的XQuatz(xorg-server),那么还需要安装libxcb, libX11, libX11-xcb, libGL, libGLU等几个包。这里面需要注意的是libGL是包括在mesa驱动包里的。一个简单的方法是直接用 sudo ports install glxgears这个命令安装glxgears这个测试程序,会自动安装GLX相关的包,然后再用sudo ports install libGLU补上libGLU就好了 。

仔细观察绘制出的图形,会发现与从零开始手敲次世代游戏引擎(十二)题图有一些区别。(有一条明显的对角线)

根据参考引用(*4),这是因为在Mac OS当中,通过XQuartz能够支持的OpenGL只能到版本2.1。所以,对于四边形的绘制,其实是拆成两个三角形绘制的,形成了明显的对角线。如果要使用版本3之上的OpenGL,必须使用Mac OS原生的图形服务(Quartz,注意少个X)和图形API(Cocoa)创建绘图Context,而不是X11 + GLX。其实在上面的过程当中也可以看到,MacPorts安装的X11是基于mesa驱动的,这个是一个开源社区的驱动,不是A/N卡原厂驱动(好吧,我的Mac其实是15年产品是I卡…但是能支持4.1),所以能够支持的功能是有限的。Mac OS原生的图形API叫Cocoa,是一个基于Object-C语言的库。另外最近还有一个新的Swift语言可以用。这部分等我自己搞明白了更新上来。

#更新2 Segmentation Fault的问题解决了。原因是External/glad/src/glad_glx.c当中,当操作系统为MacOS的时候,是按照如下的顺序查找OpenGL的动态库的(其中绿色的两行是我加上的):

diff --git a/External/src/glad/src/glad_glx.c b/External/src/glad/src/glad_glx.c
index 9551033..af0f61e 100644
--- a/External/src/glad/src/glad_glx.c
+++ b/External/src/glad/src/glad_glx.c
@@ -127,6 +127,8 @@ static
 int open_gl(void) {
 #ifdef __APPLE__
     static const char *NAMES[] = {
+        "/opt/local/lib/libGL.1.dylib",
+        "/opt/local/lib/libGL.dylib",
         "../Frameworks/OpenGL.framework/OpenGL",
         "/Library/Frameworks/OpenGL.framework/OpenGL",
         "/System/Library/Frameworks/OpenGL.framework/OpenGL",

原来的3行实际上加载的都是MacOS自带的OpenGL库。这个库是没有GLX相关的接口的。查看的方法是使用nm命令,如下:

nm -gU "/System/Library/Frameworks/OpenGL.framework/OpenGL"
00000000000053c8 T _CGLAreContextsShared
000000000000546b T _CGLBackDispatch
0000000000008b68 T _CGLChoosePixelFormat
0000000000006b46 T _CGLClearDrawable
00000000000052e3 T _CGLCopyContext
0000000000004620 T _CGLCreateContext
000000000000aa9e T _CGLCreatePBuffer
000000000000ad12 T _CGLDescribePBuffer
000000000000a584 T _CGLDescribePixelFormat
000000000000b6cd T _CGLDescribeRenderer
0000000000004381 T _CGLDestroyContext
000000000000adba T _CGLDestroyPBuffer
000000000000a0e8 T _CGLDestroyPixelFormat
000000000000b69e T _CGLDestroyRendererInfo
000000000000ccd2 T _CGLDisable
000000000000cc47 T _CGLEnable
0000000000008b2b T _CGLErrorString
000000000000d058 T _CGLFlushDrawable
0000000000005461 T _CGLFrontDispatch
000000000000528d T _CGLGetContextRetainCount
0000000000006a95 T _CGLGetDeviceFromGLRenderer
000000000000a8c8 T _CGLGetGlobalOption
0000000000005436 T _CGLGetNextContext
00000000000085a0 T _CGLGetOffScreen
000000000000aa45 T _CGLGetOption
00000000000077ef T _CGLGetPBuffer
000000000000af7c T _CGLGetPBufferRetainCount
000000000000cb70 T _CGLGetParameter
00000000000052b5 T _CGLGetPixelFormat
000000000000a1b4 T _CGLGetPixelFormatRetainCount
0000000000006a2d T _CGLGetShareGroup
0000000000007c21 T _CGLGetSurface
000000000000d0b3 T _CGLGetVersion
00000000000044b8 T _CGLGetVirtualScreen
000000000000ca68 T _CGLIsEnabled
000000000000a5d6 T _CGLLockContext
0000000000006024 T _CGLOpenCLMuxLockDown
000000000000b30c T _CGLQueryRendererInfo
00000000000050ef T _CGLReleaseContext
000000000000af4a T _CGLReleasePBuffer
000000000000a182 T _CGLReleasePixelFormat
0000000000005533 T _CGLRestoreDispatch
000000000000556a T _CGLRestoreDispatchFunction
0000000000005263 T _CGLRetainContext
000000000000af6a T _CGLRetainPBuffer
000000000000a1a2 T _CGLRetainPixelFormat
0000000000005480 T _CGLSelectDispatch
00000000000054ba T _CGLSelectDispatchBounded
0000000000005501 T _CGLSelectDispatchFunction
0000000000007cda T _CGLSetFullScreen
00000000000082e0 T _CGLSetFullScreenOnDisplay
000000000000a6a0 T _CGLSetGlobalOption
0000000000008403 T _CGLSetOffScreen
000000000000a9e0 T _CGLSetOption
0000000000006c25 T _CGLSetPBuffer
000000000000b24c T _CGLSetPBufferVolatileState
000000000000cd5d T _CGLSetParameter
000000000000788f T _CGLSetSurface
000000000000455c T _CGLSetVirtualScreen
000000000000b095 T _CGLTexImageIOSurface2D
000000000000af8e T _CGLTexImagePBuffer
000000000000a608 T _CGLUnlockContext
0000000000008671 T _CGLUpdateContext
000000000000a1c6 T _GLCDescribePixelFormat
0000000000007758 T _GLCGetPBuffer
000000000000ce49 T _GLCGetParameter
0000000000001fae T _GLCGetProfilerStorage
0000000000007be2 T _GLCGetSurface
0000000000004405 T _GLCGetVirtualScreen
0000000000001f83 T _GLCSetProfilerStorage
0000000000006050 T _cglBadApplicationNotMuxAwareLockDown
0000000000001870 T _glcDebugListener
00000000000029c1 T _glcGetIOAccelService
0000000000001fe8 T _glcNoop
00000000000027b1 T _glcPluginConnect
0000000000001ff0 T _glcPluginCount
00000000000027e1 T _glcPluginDisconnect
0000000000008b5f T _glcRecordError

可以看到很多CGL的接口,这个才是macOS原生的OpenGL接口。

所以通过追加(也可以替换)到我们通过MacPorts安装的库的路径,就可以解决这个问题。注意如果是直接安装的XQurtz的dmg包而不是通过MacPorts安装的库,那么路径应该是/usr/X11而不是/opt/local

参考引用

  1. macOS – Wikipedia
  2. XQuartz
  3. The MacPorts Project
  4. Overview of OpenGL Support on OS X
  5. CMake, OpenGL, and GLX on OS X

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 注销 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 注销 /  更改 )

Connecting to %s

%d 博主赞过: