游戏引擎开发:Task系统

一.前言

第一次在这里写技术文章,请多指教。做了很多年游戏开发了,我很幸运,一入行就做的游戏引擎方面的工作,这么多年碰到的坑也不少,所以写下来一些经验分享给大家,希望对新人能够有个指引。

在游戏引擎当中,有一个模块很少被大家提及,也许是因为它太基础了,不值得一提,但也有很多人根本不知道它的存在,那就是Task系统。

二.详解

之所以把这个模块拿出来讲,是因为这个模块是比较基础的模块,它本身非常简单,却在很多场合下可以短平快地解决问题。它封装的是一个任务队列,先进先出,所有先加入进去的任务会先执行,后加入进去的任务会后执行,我管这个模块叫做TaskManager。这个模块同时提供两种完全不同的任务队列,一种是子线程任务队列,而另外一种是主线程慢速任务队列。

首先看子线程任务队列,我管他叫ThreadTaskManager,它首先会创建一个线程,接受主线程加入的任务,而后在子线程执行,最后在再主线程执行此任务完成的回调。


游戏引擎开发:Task系统_第1张图片
主线程用于接受task,以及执行每个已经执行完毕task的回调


游戏引擎开发:Task系统_第2张图片
线程当中真正执行每一个task,并且把执行完的task投回给主线程等待执行回调

再来看主线程慢速任务队列,我管它叫TodoManager,它接受主线程加入的任务,而后在每帧Update的时候执行这些任务。之所以叫慢速队列,是因为每帧只留给它2ms的时间去执行,2ms时间到,就不再执行任务,而放到下一帧执行。这也是这个TodoManager的精髓所在,将一个大的任务分拆成多个小任务,在不同的时间片内完成。


游戏引擎开发:Task系统_第3张图片
慢速队列中执行的任务,每帧最多执行2ms

在主线程当中添加Task,在每帧Update执行,但每帧总执行时间仅限于2ms.

三.TaskManager的使用范例

TaskManager的作用,是在子线程和主线程中按序执行任务,这两个配合使用可以解决很多棘手的问题。

举例1,运行时创建Bumpmap

有一个技术叫做Bumpmap,可以理解为它是制作法线贴图方法的简化版本。一般法线贴图需要美术去做高模来生成,但是真正开发过程中,很有可能因为工期原因根本来不及做高模。现在这个古老的技术派上用上了,它的原理就是根据美术提供的纹理每个像素周围像素的亮度差,这里称之为像素的梯度,来计算出一张法线贴图。具体原理不知道的可以搜索一下,原理很简单易懂,现在的问题是,如何应用到引擎当中。

最直接的办法,就是每张需要用到Bumpmap的模型,都离线为其制作一张,作为资源存储下来,运行时加载。不过我不想这么做,我希望运行时创建。下面来拆分一下步骤:

1.加载原始纹理

2.lock该纹理,取出所有像素值

3.针对每一个像素计算出需要的值,这些值存在跟原始纹理同样尺寸大小的内存中

4.创建一张纹理,lock,填入刚刚计算好的数据,unlock

至此,bumpmap创建完毕。

如果是运行时创建,很明显4步操作会非常耗时,特别是bumpmap如果运用到其他玩家的角色身上,随着其他玩家进入视野,一秒之内如果被请求创建十几张这样的纹理,玩家的感受就是不停的卡顿。为解决这个问题,下面就该TaskManager登场了,上面4个步骤被进一步拆分为5个Task:

1.ThreadTask:加载原始图片文件,文件内容放入到内存中

2.TodoTask:通过内存数据创建纹理

3.TodoTask,Lock该图片纹理,取得所有的像素数据

4.ThreadTask:在线程中处理像素数据,逐像素计算出bumpmap,数据存放到一块新的内存中

5.TodoTask:通过bumpamap内存数据创建新的纹理。

凡是图形相关的API,全部都不允许在子线程执行。TodoTask被限制在无论有多少待执行的Task,每帧只运行2ms,而线程执行的task,根本不会对主线程帧率造成影响。这5个拆分后的Task,使得创建过程先后在子线程和主线程中交替执行,虽然流程上更加复杂,但避免了帧率的大幅抖动,从而在保证帧率一如既往平滑的前提下,完成BumpMap的创建。

举例2. 几帧之内的大量创建请求

在游戏运行过程当中,很容易出现几帧之内的大量创建其他玩家的请求,这种集中创建的请求非常容易造成卡顿。这时就需要通过TaskManager进行创建。

如果一帧有10个骨骼模型要创建,就创建10个TodoTask,每个task中还会创建不同的TodoTask和ThreadTask来完成创建工作。这样一来,当玩家控制的角色走进人群时,由于TodoTask的每帧只执行2ms、ThreadTask完全不会对主线程帧率造成抖动的特性,帧率假如说之前是稳定在50帧,创建过程中,帧率会稳定在30帧,创建全部完毕后帧率会回升至35帧左右,而过程中不会出现任何卡顿的现象,画面则表现为其他玩家角色在零点几秒之内先后出现。

四.结语

Task系统的最大优点,就是其不会对帧率造成大幅度抖动。在遇到频频卡顿难以优化的时候,TaskManager让如何优化大量占用主线程时间请求的工作,变成了如何细致地拆分成各个Task的游戏,从而解决问题。如果运用得当,游戏加载过程不会让人察觉到卡顿,会如行云流水一般顺畅。唯一要注意的问题就是,单个TodoTask执行的时间要严格限制,不能高于2ms,也就是说,单个TodoTask的内容要足够简单,如果只是一个任务就执行了1秒钟,这就说明任务拆分得有问题,要重新考虑更精细的拆分。

你可能感兴趣的:(游戏引擎开发:Task系统)