请选择 进入手机版 | 继续访问电脑版

开源计算机图形学社区(Open Source Computer Graphics Community) |OpenGPU Forum (2007-2013)| OpenGPU Project

 找回密码
 注册
搜索
查看: 5448|回复: 27

游戏引擎多线程(送给那些想了解游戏引擎多线程的人) [复制链接]

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-3 12:28:39 |显示全部楼层
http://user.qzone.qq.com/79134054/main#!app=2&via=QZ.HashRefresh&pos=1356857483

看论坛里面有人问关于引擎多线程的问题,现在常用的多线程一个是多线程加载,一个是多线程 更新,一个是多线程渲染。所有技术都在我引擎里面应用,而且里面部分技术已经用到了我们项目里面,效率和性能可以完全得到保证。

这个文章是我今年年初写的,这个文章讲解了,游戏引擎中多线程的更新和多线程的渲染问题,唯一没有详细写出的就是多线程加载(其实这个不难),但这几个关于多线程方面的协调问题,都会在本篇文章提到。

要求读者有基本的多线程编程概念,其实就是操作系统里面的PV操作 ,对D3D 足够的认识。

这篇文章会把关键代码和编程时候遇到问题都会一一解答。因为我确实遇到很多坑,但最后都被一一化解。

因为现在在做自动property IO(从此放弃了各种IO代码书写,消除了讨厌的版本号控制) ,relfect(类似UNITY3D 可以自动反射到UI面板),clone 机制(不用手动写每一个类的clone代码) ,后续我会把这个技术也写成文档,公布于大众。
3

查看全部评分

Rank: 12Rank: 12Rank: 12

注册时间
2012-6-25
积分
574
发表于 2013-7-3 13:42:17 来自手机 |显示全部楼层
你怎么解决多线程碰撞问题,就是不同线程宣染移动物体碰撞,还有把不同线程处理的物体渲染在同一光照下,

使用道具 举报

Rank: 12Rank: 12Rank: 12

注册时间
2012-6-25
积分
574
发表于 2013-7-3 13:47:06 来自手机 |显示全部楼层
一个线程更新,一个渲染 那谁都会,一般程序都是这样,现在要处理的是多个线程渲染,多线程更新,
1

查看全部评分

使用道具 举报

Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24

注册时间
2009-3-31
积分
6261
发表于 2013-7-3 14:29:57 |显示全部楼层
本帖最后由 空明流转 于 2013-7-3 18:11 编辑

渲染的时候GPU基本已经满载了,你再多的渲染线程,除了增加Rendering Context切换的消耗外,都没有任何意义。

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-3 16:10:43 |显示全部楼层
yulinqer 发表于 2013-7-3 13:47
一个线程更新,一个渲染 那谁都会,一般程序都是这样,现在要处理的是多个线程渲染,多线程更新, ...

一看你就没仔细看,更新是多个线程更新的!你想用几个线程都可以。
多线程渲染是避免GPU和CPU相互等待,你底层驱动对于渲染是不能同时处理多个的,也就是说 GPU不象CPU是多核,你再多的线程做渲染毫无意义

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-3 16:13:08 |显示全部楼层
空明流转 发表于 2013-7-3 14:29
渲染的时候GPU基本已经满载了,你再多的线程,除了增加Rendering Context切换的消耗外,都没有任何意义。 ...

多线程渲染是避免GPU和CPU相互等待,更新和渲染并排去运行,可以提高很大的运行效率。并且切换的开销可以从设计程序上完全可以规避。

使用道具 举报

Rank: 12Rank: 12Rank: 12

注册时间
2012-6-25
积分
574
发表于 2013-7-3 16:16:00 来自手机 |显示全部楼层
楼上说的对

使用道具 举报

Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24

注册时间
2009-3-31
积分
6261
发表于 2013-7-3 18:09:38 |显示全部楼层
本帖最后由 空明流转 于 2013-7-4 19:54 编辑
32220937 发表于 2013-7-3 16:13
多线程渲染是避免GPU和CPU相互等待,更新和渲染并排去运行,可以提高很大的运行效率。并且切换的开销可以 ...

我是在回复三楼。。。不是在回复你。所以我实际的意思是不要开多个渲染线程。

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-9-5
积分
2682
发表于 2013-7-3 21:53:37 |显示全部楼层
本帖最后由 clayman 于 2013-7-3 22:11 编辑

拜读了lz的文章,感谢分享~
关于开头关于更新依赖的问题,其实可以用bucket update解决,类似game engine architecture里的方法,集成到现有引擎里其实不是太麻烦。

还有“.最后一个就是创建D3D设备的时候要用多线程标志”
对这一段比较有疑问,D3DCREATE_MULTITHREADED是dx9需要尽量避免使用的特性之一,本质是对dx所有函数包了一层,每次调用都会有额外同步代码的消耗。个人觉得对于dx11之前不支持多线程的api,渲染做多线程其实意义不大。建议参考 http://www.microsoft.com/en-hk/download/details.aspx?id=18367  

使用道具 举报

Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

注册时间
2009-10-18
积分
5405
发表于 2013-7-3 22:11:33 |显示全部楼层
本帖最后由 DavidLee80 于 2013-7-3 22:36 编辑

我也认为在DX9下,构建真正的渲染多线程是不可能的,那个dx9下的多线程标志一旦开了,对性能影响很大,而诸如在dx9下自己构建类command buffer的机制去实现的多线程,对于渲染来说,还是单线程的,虽然dx11的多线程是需要driver支持才行,但是个人认为dx11是更适合渲染多线程的,目前,对于dx11的多线程渲染架构,资料不是特别多(至少我找到的不是特别多),那位大牛找到好的资料,希望多多分享啊!呵呵!

最后,还是很感谢楼主对于引擎多线程架构的分享,大家多多讨论!
补充一个问题,我忘记在哪里看到的了,大概意识就是说在DX9下,如果没有开那个多线程标志的话,资源创建(主要就是VB、IB)所在的线程必须和创建device是同一个线程,哪位大牛帮忙确认下,是这样么?

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-4 10:23:53 |显示全部楼层
DavidLee80 发表于 2013-7-3 22:11
我也认为在DX9下,构建真正的渲染多线程是不可能的,那个dx9下的多线程标志一旦开了,对性能影响很大,而诸 ...

资源都是主线程创建的,如果放到渲染线程创建,会给资源管理带来麻烦,一般资源管理都是主线程管理,还有一个如果放到IO线程去创建,这样渲染线程使用的时候同步也会出现一些问题。
所以渲染线程只负责使用,创建和销毁都不会在渲染线程,IO线程只处理IO,也不会创建,所有资源创建都在主线程。

D3D 比较让人诡异的是,我资源A 在主线程创建,渲染线程同时再用资源B 的LOCK,这样它就会出问题(如果不需要异步加载,或者修改异步加载的方法,等到2个线程同步的时候,再去创建资源,或者资源在进游戏都创建好,渲染线程资源怎么lock,我试验过都是没事的)按道理来说 资源A 和B是不同资源,我再2个线程里面应该不会有问题,我文章里面也提到了,有可能D3D创建资源函数和 资源接口对应的函数调用了,同步相关的东西,看不到D3D的代码,所以这个是猜想,但我敢肯定95%以上是这个原因。


至于你说的资源创建和device 不在同一个线程,我没有实验过,我device创建和资源创建都在主线程。

还有一个想说的是,D3D多线程标志开启对性能的损耗,按常规来说,D3D如果开启这个标志,会在每个函数里面加一些同步相关的代码,虽然我不知道它怎么实现的,但以我的经验来讲,如果你可以避开这些同步,完全是没有多少性能损耗的,所谓的避开同步,就是不要让2个线程都调用D3D相关的东西,但这往往是不可能的,所以就要减少同时调用,比如上面说的,创建资源在主线程调用,同时渲染线程也在调用D3D其他相关的东西,这种情况其实很少有,大部分D3D相关的要么在游戏启动之前就已经创建好了,所以渲染的时候,主线程一般不会跑任何D3D的东西,这样D3D底层同步操作,基本也不会耗费什么时间的。

最后一个注意的是,如果创建device和D3D其他API调用 不在同一个线程,开启D3D debug 会有warning。
但只要避开我上面说的,不开启也不会奔溃。

所以完全在使用可以规避这种风险,unreal3他也开启了这个标志位。

使用道具 举报

Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24

注册时间
2009-3-31
积分
6261
发表于 2013-7-4 10:47:26 |显示全部楼层
32220937 发表于 2013-7-4 10:23
资源都是主线程创建的,如果放到渲染线程创建,会给资源管理带来麻烦,一般资源管理都是主线程管理,还有 ...

因为Driver经常会让Device有一个池,这个池就是拿来分配给你Lock后返回来的内存的。然后如果这个池不是线程安全的话。。。

使用道具 举报

Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

注册时间
2009-10-18
积分
5405
发表于 2013-7-4 11:05:33 |显示全部楼层
32220937 发表于 2013-7-4 10:23
资源都是主线程创建的,如果放到渲染线程创建,会给资源管理带来麻烦,一般资源管理都是主线程管理,还有 ...

我曾经在我们的引擎里测试过,开了那个DX9的多线程标志,会慢很多,可能我们目前的多线程机制不是很好导致的,或者其他什么原因,目前我还没有明确的结论,楼主说unreal3开了这个标志,那么他应该做了与楼主类似的工作(我没仔细看过),我觉得还有一个参考就是cryengine,楼主可以看看cryengine里面是否也开了这个标志,因为我觉得从性能上来说,cryengine要比unreal好很多,所以cryengine也应该做过类似的工作(我指的是他里面那个DX9的封装层)

使用道具 举报

Rank: 13Rank: 13Rank: 13Rank: 13

注册时间
2013-6-23
积分
1128
发表于 2013-7-4 11:10:26 |显示全部楼层
32220937 发表于 2013-7-4 10:23
资源都是主线程创建的,如果放到渲染线程创建,会给资源管理带来麻烦,一般资源管理都是主线程管理,还有 ...

高人!小弟想把Task based multithreading加入到自己的东西里面,但是不知道自己的实现是否有效,像请教高人是否有相关的文章可以推荐。

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-9-5
积分
2682
发表于 2013-7-4 22:57:26 |显示全部楼层
lz没有看我给的链接吗

Multithreaded devices wrap every API call in synchronization code
   Architected to work in sloppy multithreaded legacy codebases
   Some titles don’t mind the coarse sync overhead

Fully asynchronous resource creation not possible
   True for both D3D9 and D3D10
   Resource creation, lock & unlock must be done on the rendering thread

Multithreaded Submission?
   Not possible with D3D9 or D3D10
   Implement API call display lists?
        Removes scene traversal overhead from render thread
       Generally do not have good performance



Lock & Unlock on D3D9 can have strange behavior when they span frame boundaries
   Untested code path
   After the Unlock, execute a quick Lock & Unlock in succession to avoid corruption

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-5 11:53:25 |显示全部楼层
clayman 发表于 2013-7-4 22:57
lz没有看我给的链接吗

Multithreaded devices wrap every API call in synchronization code

这篇文章我看过,我在做多线程渲染前,搜集了所有资料和demo,他的这个文章就是我文章中提高的,典型的 只有理论,没有任何实现细节 的 并且误导大众的文章。

多线程标志 可以不开,只要你渲染线程和主线程不同时调用 D3D API 就可以(但开启D3D debug 会有warning,提示你在2个不同线程调用)。 这个设计上完全可以规避的。象其他资源创建 要在主线程创建,只要在同步的时候(渲染线程这个时候不管是否挂起,这个看你多线程架构的设计了,经验来讲最好不要挂起,因为本身你渲染线程基本每帧都在跑,挂起后再唤醒,会有线程切换的开销,可以用其他的同步变量来实现,反正这个时候没有任何render conmand ,显卡的数据都已经提交到显示器上,保证渲染线程不会有任何D3D调用)创建就可以。

即使开启了这个标志,因为你不是2个线程同时掉用D3D API,这种同步操纵也是最小的。

我总觉的实践比任何理论都有说服力,再我没有做之前,也是有很多困惑,其实好多困惑都是受到那些只有理论没有demo 那些文章所导致的。

说实话,多亏有unreal,它实现了,并且用到了成功的项目,我只是再它的基础上改进了,改进了机制和效率,使它效率尽量最高,多线程渲染没有那么难,好多困难都是被歪曲的。

而且CPU 和GPU 可以并排去跑,只要你的程序不是 严重的 GPU 瓶颈,速度 是有很大的提升,如果你CPU相对耗时比GPU 多,那么再加上多线程更新,还会更大提高。

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-9-5
积分
2682
发表于 2013-7-5 23:38:09 |显示全部楼层
我们之前的引擎就是这么做的,有dx9的多线程调用,没开多线程标志,大部分时候都没问题,但偶尔会出现崩溃的情况,最后分析就是d3d如果不开标志,跨线程是有一定几率会出错

使用道具 举报

Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

注册时间
2010-3-15
积分
1062
发表于 2013-7-7 16:35:15 |显示全部楼层
clayman 发表于 2013-7-5 23:38
我们之前的引擎就是这么做的,有dx9的多线程调用,没开多线程标志,大部分时候都没问题,但偶尔会出现崩溃 ...

实在不明白为啥要在不同的线程里调用同一个d3d device的api,为啥会存在这种设计?

使用道具 举报

Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

注册时间
2010-3-15
积分
1062
发表于 2013-7-7 16:46:05 |显示全部楼层
这里面每一个过程都是相互依赖的,上一个流程输出,是下一个流程输入,一般只要是相互依赖的,要想做多线程处理,每一帧去同步的话,都要有2个buffer,上一个流程用一个buffer把上一帧的结果记录下来,下一个流程去取另一个buffer进行出来,然后帧末或者帧前交换2个buffer。如果每个流程都去这么做,随着流程越多,本来是延迟一帧的做法,随着流程的增多,会延迟很多帧,并且,好多东西都是不固定的,buffer来存放也是很棘手的问题。


以上这段楼主博文里的话,有些不理解。这个问题本质上不是求解dependency graph的过程么?难道每次求值前不能确定有向图中节点的顺序?况且实现分析一下dependency graph还可以在后续真正求解的过程中更好的利用上多线程。
楼主的文章里基本还是围绕D3D API的调用来说的,其中的方法很好的解决了CPU等GPU的问题(虽然有些方法driver层有做了,而且更优雅),但似乎没有提到如何解GPU等CPU的问题。另外对于这类问题(D3D API调用时造成的sync/flush),我觉得最好的解决办法还是buffer, SLI(或其他multi-gpu方案)之所以能快,就是因为它buffer了更多的帧。

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-7 23:41:48 |显示全部楼层
Россия 发表于 2013-7-7 16:46
以上这段楼主博文里的话,有些不理解。这个问题本质上不是求解dependency graph的过程么?难道每次求值前 ...

你没明白我的意思,我这么说的意思就是简化了问题,你不可能让相机裁减和物体更新同时进行,也就是说必须经过相机裁减,我知道那些物体可见了,然后这些物体才去更新。所以物体更新来源于相机裁减的结果。

其实文章中重点是多线程更新和多线程渲染,多线程更新只是讲了架构没有讲细节,因为下面讲的多线程的细节render command 完全可以复用到多线程更新,稍微有些心计的人应该就可以怎么用render command 方法去实现多线程更新,多线程渲染已经说的很详细,自己去扩展到多线程更新,如果你真看懂了其实很容易,而且还不涉及D3d相关东西

其实这个架构就是侦同步,这个不涉及到解决谁去等待谁的问题,只是每帧2个线程一起跑,如果一个线程先跑完,那么这一个线程就要等待另一线程完成,然后2个线程同步。所以只要一个线程有严重瓶颈,那么多线程方法就没有任何效率提升

使用道具 举报

Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

注册时间
2010-3-15
积分
1062
发表于 2013-7-8 09:38:27 |显示全部楼层
关于那段话,我觉得是你没懂我的意思。。。。
我提到了dependency graph的概念,你就不应该认为我不知道你所说的物体的“更新”之间依赖关系的存在。你所举的view frustum culling的例子,当然相机和物体不能同时(simultaneously)计算,因为他们呢之间有依赖关系. 但两个没有联系的角色或NPC的skin mesh的计算是完全可以同时做的。 既然是讨论架构或框架,当然希望概念能抽象一些。我不明白的是你所说的”随着流程的增多“(可以理解为DG变的复杂了?), “会延迟很多帧”(这里帧的概念是游戏里的一个tick还是一次back buffer present,还是其他?)

render command的设计我觉得我的理解和你一致,大牛不必多费口舌介绍,虽然细节上还有可以推敲的地方(比如你说的“双buffer机制“)

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-8 10:14:30 |显示全部楼层
Россия 发表于 2013-7-8 09:38
关于那段话,我觉得是你没懂我的意思。。。。
我提到了dependency graph的概念,你就不应该认为我不知道你 ...

“随着流程增多” 就是我介绍多线程模型的时候,每个模块都输出 是 下一个模块的输入,如果这样类似的模块在游戏里面划分的很多,并且这些模块不是等它上一模块的结果,让他们并排去跑,并且采用双buffer 机制,就会延迟很多帧

比如 我现在有流程 1 到5,执行的顺序是 从 1到5,并且 1的输出是 2 的输入,2个输出是3的输入,以此类推,但我为了让他们并排去跑,而不是等待上一阵的结果再去执行,那么每个流程就要有一个双buffer,去缓冲上一帧的结果,如果流程1在处理第N帧,也就是当前的帧数据,那么流程2为了可以和流程1并排跑,那么它肯定处理上一帧缓存的数据,也就是第N-1帧的数据,以此类推,第5个流程应该在处理的是N-4帧的数据,
如果第5个流程是渲染流程的话,那么肯定我们玩游戏以看到画面为标准,看到的肯定就不是当前的在处理的那一帧,而延后的4帧。同理也就可以判断,前4帧其实画面都根本没有显示出来,只是在每个流程里面做缓冲,过了前4帧,画面才显示出来。

所以如果流程很多的话,这样并排去跑,肯定要延迟很多帧。

使用道具 举报

Rank: 13Rank: 13Rank: 13Rank: 13

注册时间
2012-11-27
积分
1439
发表于 2013-7-8 10:56:30 |显示全部楼层
自动property IO(从此放弃了各种IO代码书写,消除了讨厌的版本号控制) ,relfect(类似UNITY3D 可以自动反射到UI面板)

这个是不是就是boost的序列化啊,LZ要自己实现吗?最近自己搞了一套“运行时定义类成员参数”的方法,目的也是为了消灭IO部分的编码,实现了之后才想起来有boost序列化这个东西,不过一直没用过,最近正想研究一下。

使用道具 举报

Rank: 21Rank: 21Rank: 21Rank: 21Rank: 21Rank: 21

注册时间
2012-10-31
积分
8056
发表于 2013-7-8 11:50:54 |显示全部楼层
最近做移动设备的多线程渲染,都是opengles2.0的东西。openggl没有多线程的概念。
render command也是我们选择的设计,command和data封装在一起,涉及data是copy还ref,对于效率最好是ref,并且ref是就不要有lock。而且主流移动设备现在都至少2核CPU,但GPU通常较弱,很多计算都考虑cpu并行计算。

使用道具 举报

Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

注册时间
2010-3-15
积分
1062
发表于 2013-7-8 12:49:31 |显示全部楼层
32220937 发表于 2013-7-8 10:14
“随着流程增多” 就是我介绍多线程模型的时候,每个模块都输出 是 下一个模块的输入,如果这样类似的模 ...

我觉得这是不合理的设计啊,为什么一个对象这一帧的更新要用到另外一个对象在上一帧时的状态?
当然“某某引擎是这样做的”可以成为一个很牛逼的理由。。。。

使用道具 举报

Rank: 16Rank: 16Rank: 16Rank: 16

注册时间
2009-10-21
积分
2618
发表于 2013-7-8 13:08:43 |显示全部楼层
Россия 发表于 2013-7-8 12:49
我觉得这是不合理的设计啊,为什么一个对象这一帧的更新要用到另外一个对象在上一帧时的状态?
当然“某 ...

“为什么一个对象这一帧的更新要用到另外一个对象在上一帧时的状态?”这句话 我没理解!
我一直说的都是模块和流程,没提到具体哪个对象呀!

其实文章介绍的多线程渲染,就延后了一帧,渲染和更新分别再2个线程里面跑,当前渲染处理的数据其实都是上一帧数据。

使用道具 举报

Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24Rank: 24

注册时间
2009-3-31
积分
6261
发表于 2013-7-8 16:51:01 |显示全部楼层
32220937 发表于 2013-7-8 13:08
“为什么一个对象这一帧的更新要用到另外一个对象在上一帧时的状态?”这句话 我没理解!
我一直说的都是 ...

非同步的渲染自然会有这样的问题啦,必要的时候要等1-2个FPS。如果为了精确性什么的减少逻辑帧的时间,可以选择减少Rendering的次数。

使用道具 举报

Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

注册时间
2010-3-15
积分
1062
发表于 2013-7-8 18:29:16 |显示全部楼层
我觉得吧,需要延时>=2 frames以上的系统,都是设计问题,都可以解决。

使用道具 举报

最近看过此主题的会员

您需要登录后才可以回帖 登录 | 注册

‹‹
我的工具栏

关于我们|手机版|Archiver|开源计算机图形学社区(Open Source Computer Graphics Community) | OpenGPU Project | OpenGPU Forum (2007-2013)

GMT+8, 2017-4-23 16:04 , Processed in 0.095037 second(s), 12 queries .

Powered by Discuz! X2

© 2001-2011 Comsenz Inc.

回顶部