C++23Executors的Executors与ECS的相似性研究

C++23Executors的Executors与ECS的相似性研究

引擎叫:StarEngine

Github:

这次的调整,主要是实装了“面向数据”和“异步框架”两个特性,以及引发的一系列重构。

这篇文章介绍了引擎的整体架构,会比较长技能特效,有笔误请见谅。

主流游戏开发引擎_主流虚拟现实开发引擎_html5游戏开发引擎

比较朴素。只完成了引擎框架,画面请无视面向数据(Data-Oriented)

StarEngine并没有运用业界流行的ECS(Entity-Component-System)框架,而是开发了全新的基于Graph的面向数据框架。

ECS存在以下一些问题:

相比之下,Graph与ECS有很多相似性,理念是相通的。

主流虚拟现实开发引擎_主流游戏开发引擎_html5游戏开发引擎

Graph与ECS有着一一对应关系

Graph与ECS的区别在于我们通过泛型实现复用,而不是依赖注入。

我们的实现有以下的特点:

前人对Graph的研究非常透彻,归纳出了以下一些基本Concepts:

html5游戏开发引擎_主流虚拟现实开发引擎_主流游戏开发引擎

Graph基本分类

我们可以选择合适的数据结构来实现相应的Concept,以达到最优的性能。一般的ECS只有一种实现,不具备如此的灵活性。同时由于boost的泛型设计,具有统一的接口。这样彻底地分离了接口与实现,使得切换实现的成本非常低。boost有着大量的图论算法比如DFS、BFS,省下的开发量非常可观。

在此基础之上,StarEngine引入了更多的Concept,来解决引擎开发中的常见问题。

通过以上6种Concept的自由组合,我们能实现引擎中的绝大部分图状功能。

异步框架

上面我们已经用Graph替代了ECS,还有多线程的问题。

Unity的DOTS通过Jobs系统实现了这点,我们也需要一个多线程/异步框架。

这里我们使用的是C++23 Executors,具体实现是libunifex。

C++23 Executors

C++23的Executors包罗万象,很难概括Executors究竟干了些什么,这里尝试着罗列一下

总之,通过Executors,我们可以大幅简化异步、多线程的编写。很多写法在以前是不可想象的。

Graph+Executors

通过Graph+Executors,我们在功能和性能上是可以和DOTS+Jobs抗衡的,毕竟我们是原生C++配合泛型的零开销抽象,打不过Jobs+Burst Compiler是有点说不过去的。

开发难易度见仁见智,C++语言更难,但心智模型简单。C#语言简单,但框架更复杂。

Executors写起来差不多是下面这种感觉。

主流虚拟现实开发引擎_主流游戏开发引擎_html5游戏开发引擎

利用libunifex实现的app流程

好吧,我承认C++确实有点难懂!我第一次看executor的代码也宛如天书,但理解了之后还是比较简单的,比任何C++异步框架都要简单。

executors的表达力非常之强,整个app的框架逻辑寥寥数行就能完成。像这样的逻辑,让我用asio写很可能是写不对的。

图形引擎新特性

在有了Graph框架支持后,能做的功能就很多了,首先是图形引擎部分。

我们的图形引擎由两张Graph组成,分别是执行图(Executor Graph)与内容图(Content Graph)。

执行图 Executor Graph

执行图是对硬件平台的抽象。

现在的游戏对图形引擎非常苛刻,有以下一些需求必须满足。

这些需求是互相矛盾的,一种硬件架构不能满足全部需求。引擎需要对硬件平台进行适当抽象、使得其可更改、可配置。

主流游戏开发引擎_html5游戏开发引擎_主流虚拟现实开发引擎

Executor Graph示意图

首先Executor Graph满足Ownership Graph概念,这是对硬件父子关系的抽象。

其次Executor Graph是一个网络,不同节点(Vertex)之间可以传输数据(带宽不同)、可以进行Gpu/Cpu同步。

有了Executor Graph,我们可以根据不同平台、不同配置、不同算法、不同用途,自定义合适的硬件架构。比如:

在合理的抽象上,Executor Graph隐藏掉实现细节,用户可以通过声明式的编程加以控制,自动化、简化开发。

内容图 Content Graph

构成图形引擎的另一张图是内容图(Content Graph)。

Content Graph是对各类内容(Content)的抽象,其建立在Executor Graph之上,相关性很高,因为资源都是存放在硬件上的。

每个Content都有各自的居住证,在Executor Graph的不同节点上创建。Content可以通过UUID索引,所以是个UUID Graph。

Content互相之间有引用关系,整体构成一个DAG(有向无圈图)地图场景,包含以下这些内容:

主流游戏开发引擎_主流虚拟现实开发引擎_html5游戏开发引擎

这里解释下几个特别的Content:

Pipeline (PPL):类似Unity的Shader,决定了物体在哪些Render Queue用哪个Shader Program渲染。包含了相应的PSO管线状态。

Root Signature Graph (RSG):包含所有Shader Program以及对应的Root Signature,描述了各个Descriptor的布局、更新频率、来源。整体是个树状结构。

Resource Graph (RESG):跟踪了所有可读写资源的状态,根据不同的硬件Tier、用途、驻留,选择合适的分配策略。

Value Graph (VG):保存了GPU需要用到的数据,结构类似JSON,是个Addressable Graph。用途类似Unity的Shader.SetGlobal。

Render Dependency Graph (RDG):描述了渲染管线的Pass/Subpass流程、资源的状态转换、在哪些Executor Graph节点运行、同步等信息。

Render Queue Graph (RQG):描述了整个场景的RenderQueue排序,是个树状结构。每个节点会绑定一个Root Signature Graph节点,是多对一的关系。

Render Graph (RG):最终的渲染任务,是Render Dependency Graph的实例。决定哪些RenderQueue在哪些RenderPass渲染。绑定所有用到的资源、场景、数据。

与Frame Graph的区别

我们的Render Graph是离线制作的,这点与Frame Graph动态计算不同。Render Graph一般只是Frame Graph的子图,不是一帧用到的所有Render Pass都拿来一起优化。这有以下一些原因:

Render Graph与Render Graph之间的数据交换、状态跟踪,由Resource Graph负责。由于不是全局优化,性能可能达不到最优,但这个取舍我觉得可以接受。

主流虚拟现实开发引擎_html5游戏开发引擎_主流游戏开发引擎

我们通过Task Graph组织需要用到Render Graph,比如ShadowMap计算、场景渲染、Post-Process渲染等。Task Graph需要每帧动态构建,但粒度比较粗,可以包含CPU任务,相当于Unity的SRP。

设计上,我们希望通过Task Graph + Render Graph实现所有渲染算法。其中还有很多功能需要Scene Graph提供,比如物件剔除、地形管理、LOD管理等。这个随着版本的升级,会慢慢加入。

资产系统(Asset)

我们的资产管理系统无耻抄袭Unity,就不赘述了。

选择Unity的原因主要是更偏好UUID而不是Path。就像身份证和户口,身份证是唯一的,但户籍地址是会变更的。用UUID的话,资产移动会容易很多。

我们用UUID来实现硬引用(Hard link),用Path来实现软引用(Soft link)。

资产图(Asset Graph)

Asset Graph用于管理我们所有的资产,它拥有文件系统的树状结构。同时也是个DAG,用于表示索引关系。最后是个UUID Graph,可以用UUID索引。

Asset Graph大致有如下一些资产类型。

html5游戏开发引擎_主流游戏开发引擎_主流虚拟现实开发引擎

相比之前的Content Graph,大部分是相同的。

但是注意,这里的Asset和上文提到的Content是不一样的。Content是(Cook)处理后的产物,而Asset是原始的资产文件,比如图片、fbx等。有时它们结构上的区别会很大,比如Cook过程会把Prefab扁平化,或者把Mesh打碎成Cluster。

和Content Graph相比,这里少了Render Graph等运行时用到的内容,多了Shader Graph和Shader Module两个Shader相关的资产。

这里解释下Shader Graph和Root Signature Graph的具体用法。

Shader Graph/Shader Modules

我们的Shader Graph之前介绍过,当时没可视化看起来不直观主流游戏开发引擎,现在可以显示啦!

html5游戏开发引擎_主流游戏开发引擎_主流虚拟现实开发引擎

Shader Graph可视化

我们的优点是可以根据命名自动连线,编辑时大致排个序就行。

html5游戏开发引擎_主流游戏开发引擎_主流虚拟现实开发引擎

Shader Graph构成

比如上图中,节点自上而下一个个拼接,运行时自下而上逐个运行。编译成功就是能用的Shader Graph。

Shader Graph里的节点我们称为Shader Module,存放在Shader Modules里统一管理。

主流游戏开发引擎_主流虚拟现实开发引擎_html5游戏开发引擎

Shader Modules

为了生成最后的Shader Program,光有单个Shader是不够的。现代图形API有很强的全局性,需要通盘考虑所有资源利用与状态变换。Shader也是如此,需要整体布局Descriptor,提高命令提交性能。

我们通过Root Signature Graph来实现这一目的。

Root Signature Graph

Root Signature Graph (RSG)根据Descriptor的更新频率、Shader使用的集中度,构成一个树状结构。根节点的Descriptor更新频率最低(比如Per-Frame)、叶节点的更新频率最高(比如Per-Instance)。

在叶节点下,挂载各个Shader Program。

主流虚拟现实开发引擎_html5游戏开发引擎_主流游戏开发引擎

Root Signature Graph例子。两个RSG节点分别为Depth和Scene

RSG从叶节点的Shader Program中收集所有用到的Attribute(Buffer、Texture、Constant等),生成Constant Buffer、Descriptor布局,然后分配寄存器(register),最后构建Root Signature。

主流游戏开发引擎_主流虚拟现实开发引擎_html5游戏开发引擎

在构建完Root Signature之后,就可以生成真正的Shader Program了。

主流虚拟现实开发引擎_主流游戏开发引擎_html5游戏开发引擎

Shader Program例子

我们的渲染管线,是自下而上逐步构建的,从最小单元的Shader Module,组合成Shader Graph,再由Root Signature Graph统筹管理,最后交给Render Graph绘制。

这样做的好处是,每个部分是解耦的。

传统引擎往往要同时兼顾所有方面,又没有自动化工具,很容易出现笔误bug。

渲染部分差不多讲完了,我们来介绍下场景组织!

Prefab (Recursive)

我们的Prefab虽然叫Prefab,但没有抄Unity。

我们抄的是Pixar的USD(Universal Scene Description)。

html5游戏开发引擎_主流游戏开发引擎_主流虚拟现实开发引擎

USD是一个用于场景表达的库,主要用于影视动画制作,Unity和UE都(部分)支持。我们的实现也仅支持USD的一小部分。

USD主要由Layer构成,有点像Unity的Prefab。主要是为了解决以下一些问题:

举个例子,下图中shot_Anim动画层引用了Buzz.usd与Woody.usd,构成了动画场景。随后shot_FX.usd特效层通过sublayer的形式组合了shot_Anim.usd,并改写了一些动画。最后shot_Lighting.usd也通过sublayer组合了shot_FX.usd,进行最后的光照合成。

html5游戏开发引擎_主流游戏开发引擎_主流虚拟现实开发引擎

USD应用实例

这样做的好处是,Lighting、FX、Anim人员可以编辑自己的usd文件,互不干扰。减少了冲突,整个DCC流程会相当畅通。

游戏场景的编辑,其实也需要这样组织,开发中有时出现场景变动导致过场动画需要重做,这其实是可以避免的。

我们的Prefab对应USD的Layer,会尽量保持兼容性,目前只实现了Sublayer与Reference。希望以后可以直接导入.usd文件。

USD概念繁多,之前提到的Addressable、Path等概念都源自USD,非常值得学习。

脚本系统(实验中)

到现在为止,我们都是在用C++开发,但C++比较难写,我们引擎的写法又相对非主流(非依赖注入主流游戏开发引擎,不反转控制),不适合一般游戏逻辑的编写。再加上有热更新需求,脚本系统是必须要有的。

我们希望最终用户使用脚本编写游戏。

游戏的脚本语言选择其实不多,Lua、JavaScript(TypeScript)、C#是比较主流的脚本语言。

我们选择的是JavaScript,原因有以下几点:

具体的落地方案,我们选择的是集成React-Native。

React与React-Native

主流游戏开发引擎_html5游戏开发引擎_主流虚拟现实开发引擎

React

React与React-Native都是facebook的开源库。

React是JavaScript的UI库,主要用于Web前端。React-Native(RN)则是基于React的跨平台App框架。

为什么选择集成这么个看起来和游戏开发没什么关系的库?有几点原因:

主流游戏开发引擎_html5游戏开发引擎_主流虚拟现实开发引擎

React Native现在的架构

ReactNative有几个线程值得注意

我们已经实现了ReactNative的后端,可以跑游戏脚本。但UI部分暂时还未全部支持。

编辑器

编辑器界面用的Dear ImGui,虽然很想用我们的React-Native来实现,但Dear ImGui实在太香了。预计短期内不会替换。

由于我们的架构比较面向数据,在没有反射、对象标注的情况下,也能实现Inspector。

我们通过Executors实现了Active Object设计模式,管理数据的读写冲突。

多亏了Executors,写编辑器还是比较容易的,整体是多线程异步的,用起来没什么卡顿。遇到有些复杂操作很难原子化的时候,我们会弹出进度条阻止用户进一步操作,比如打开场景。

展望

至此我们完成了一个最低限度的游戏引擎,有渲染、有脚本、有资产导入。

未来还有更多的功能可以实现,目前的规划是:

结语

真心感谢看完的朋友,希望这篇文章能激发大家更多的灵感。

有很多细节没有具体展开,以后再做详细介绍吧。

来源知乎专栏:Star游戏引擎开发记录

最后,各位观众,如果你有什么技术想分享给大家,可以公众号留言。

文章来源:http://mp.weixin.qq.com/s?src=11×tamp=1684718713&ver=4543&signature=iU9gqP-v1ksErHl1s8u9MSy5oblG4pmgFaEjw4H4K10GHZOXh3KynqXnM8NJeSYvTqEZwrPLRVQhPLmuPo4LM8vz60*ticQD2FYrqQH3aZAXm6fHiwa1vUT2CZePaiaL&new=1