MMO游戏中心渲染架构——高质量游戏在“低配”设备上的丝滑体验 - 知乎

与大多数其他软件相比,MMO游戏客户端都是GPU开销大户,而玩家多开客户端是非常常见的行为。伴随着多开,内存开销和GPU显存开销都会成倍增加。对于大多数配置不是太高的电脑(包括笔记本)来说,显存压力过大,显卡驱动就会频繁的将部分显存交换到内存中,进而带来游戏特别卡顿、风扇狂转、机箱发热,甚至游戏崩溃等情况,严重的影响了玩家的游玩体验。实际上,同一个游戏客户端,在玩家多次启动登录进入游戏时,不少资源是可以共享的。本文将介绍新倩女幽魂如何共享显存方面的工作。

一.需求

新倩女幽魂是一款pc端游,是雷火的第一款成功游戏,也是雷火运营最长的游戏,到2021年4月份已经成功运营10年。从制作初期,就设定为允许玩家多开。我们在2020年下半年,对倩女完成了次世代引擎的迭代升级,将游戏画面效果提升到了一个新的高度。不过更好的效果,必然会用到更大的资源量,如何在效果好的同时,又能尽可能降低开销,提高性能,一直是工作的一个重点。当经过很长一段时间的性能挖掘后,我们认为在当前架构下该做的都做了。但一直有一个声音在耳边回响:“我们还能优化吗?“

共享是一个很好的思路,我们前期做了内存方面的共享,收获到很好的效果,由此可以想象,如果显存也能做共享,那无疑可以给大量硬件不是太高的玩家多开时降低其开销,优化性能。

二. 调研梦幻

玩过梦幻西游的同学知道,梦幻有一个“中心服务”进程,似乎是做什么共享的。 梦幻是网易一款非常成功而古老的游戏,它是一款2D游戏,基于dx9和32位系统。出于对其“中心服务”进程的好奇,对它做了一番调研。

梦幻的做法是,当游戏客户端启动,会额外启一个“中心服务”进程(即便把它kill掉,它也会自启动),把各客户端换装系统里需要展示的3D模型,渲染成2D贴图,之后通过内存共享的方式,同步给客户端用于展示,解决了换装贴图组合爆炸的问题。其架构大致如下图展示:

上述梦幻西游的共享方案,有以下几个问题:

  1. 并非真正的跨进程显存共享,实际只是一个公用的3D模型渲染器,用于处理2D游戏换装系统中贴图组合爆炸这一游戏系统中一个很小的子问题。真正的游戏整个渲染系统依然在各自的客户端执行。
  2. 实时性不高,通过内存共享的机制把资源交给中心服务器渲染完,之后再通过内存共享的机制返回给客户端。每个客户端要展示,还需要额外的渲染贴图操作。整个过程并不是在显卡GPU层面执行,需要涉及到从显存取出结果,通过内存分享,再在各自客户端渲染到显存。来回一次消耗大量时间,该方案只能用于一些实时性要求不高的应用场景

由此可知,梦幻的方案并不适合我们。

三. 前期预研

了解到DX11下有一个函数:OpenSharedResource(https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11device-opensharedresource),这个函数的功能是可以做显存资源共享。我在游戏中做了如下的测试:

1. 在一个客户端将某张贴图创建好后,在另一个客户端通过OpensharedResource获取,是可以成功使用的,不过看不出来对性能有什么提高。

2. 进一步测试,将一个客户端下所有贴图都创建成可共享的,另一个客户端通过共享的方式去打开,游戏本身没问题,但通过对比测试可得知,游戏占用的GPU开销,与不共享的客户端多开对比,并没有明显降低

由此猜测,OpenSharedResource在多个进程之间共享显存,在显卡驱动底层,应该是拷贝了多份,避免出问题。所以只是调用这个函数来共享资源,也达不到我们要的节省显存之目的。

3. 最后又做了另一个测试:将一个客户端渲染的结果RT,通过共享给另一个客户端输出,成功。

所以,如果要真正达到降低显存的目的,得要把游戏的架构调整一下,即:创建一个“中心渲染”进程,把所有的资源和渲染指令都交给它负责,每个客户端拿到一个渲染结果的RT,输出到各自客户端下。这个思想有点像云游戏的思路,所不同的是,本地启动的“中心渲染”进程提供了云渲染服务,相当于本地云游戏。

四. 核心思路

中心渲染的核心思路,主要有以下几点:

1. 保证客户端与中心渲染交换信息的实时性

即:每个客户端当前想要做渲染,中心渲染能立即处理渲染指令,并快速返回渲染结果到对应客户端。为了做到这一点,将原来直接对DX API的调用操作抽象了一层;引入内存共享,在中心渲染与客户端之间,共享同一块内存,在这块内存中,通过不加锁的Ringbuffer(环形缓冲区)发送客户端渲染的DX API命令。中心渲染进程会开启多个线程,每个线程处理一个客户端的RenderCommand(渲染指令),并通过DeferredContext将命令汇总起来,在一帧渲染结束时,执行FinishCommandlist把命令打包,最终在中心渲染的主线程做ImmediateContext的ExecuteCommandlist,渲染结果拷贝回各自客户端所指定的RT。同时需要处理多个客户端之间数据的隔离,不能出现多个客户端之间串流数据的情况。

2. GPU资源的共享

对于创建资源的操作,比如创建贴图,通过把贴图路径计算hash值,在中心渲染进程,可以查表获取该hash的资源是否已经创建,如果已经创建,则无需重复创建,直接将其引用计数+1并返回结果即可。同理对于shader、mesh等数据也做一样的处理,如此多个客户端同一份资源,在中心渲染进程中,只有一份。

五. 架构及实现细节

整个中心渲染的架构,大致如下图所示

具体技术细节,参照图中标注,依次为:

1. 游戏客户端所需要调用的所有DX API都将经过Center DX API层进行调用,该层会将相关的调用进行封装,将数据放在合适的位置,发送给中心渲染进程。

2. 基于共享内存技术,在游戏客户端与中心渲染进程之间,同享了一份内存,依赖该内存将客户端渲染相关API指令发送到中心渲染进程。其中API命令本身储存在Ringbuffer中,实现无锁访问;这种架构下,中心渲染读取渲染指令速度非常快,与在本客户端直接渲染的效率基本一致,无延时。

3. 中心渲染下的多线程架构,每个线程创建DeferredContext处理各个客户端发过来的指令,客户端之间的渲染互不干扰,在每帧结束时,会执行FinishCommandList,提交到主线程执行ExecuteCommandList实现真正的渲染。

4. 把渲染结果,通过CopyResource拷贝回一份“共享的贴图”,此贴图是通过每个客户端自己调用CreateTexture2D创建RT,但其MiscFlags设置为D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX,在中心渲染的Device上,通过OpenSharedResource做成跨Device设备的资源。直接在显卡GPU底层达到共享。最后每个客户端把这张RT呈现在自己的屏幕窗口上。

5. 多个中心渲染的线程之间,实现GPU资源的共享,当每个客户端发过来的指令是创建资源(比如创建贴图),则算出对应贴图路径的hash值(通常是一个64位整数),根据这个hash查找对应全局表(或者在全局表中创建对应资源)。每份资源只允许创建一次,重复创建情况下只是引用计数的增加。

六. 成效

当中心渲染架构已基本完成后,本地三开,任务管理器里看到的性能截图:

从上图可见,中心渲染架构后,游戏 本身客户端所占GPU开销已经很低很低,都集中在中心渲染进程。

经过自动化压力测试(测试机:Win10,i7-7700, 16G, 机械硬盘, GTX1060),关键性能对比数据如下:

1. fps (6开多人压测)

2. 总GPU占用(3开)

3. 专用内存集(3开)

从测试结果可知,启用了中心渲染架构后的客户端,在fps基本不变的情况下,总的显存占用有所下降(如果是可共享的资源更多,则会更省,比如在同一个场景下),符合我们的预期。

七. 总结

新倩女幽魂作为一款老游戏,如何在原有游戏内容上推陈出新,性能上不断迭代和优化,是我们开发人员要一直思考的问题。从近期很火的云游戏的概念中获得启发,我们实现了客户端中心渲染的架构,对于客户端多开有了一定的提升。另外一个好处是,由于资源的集中管理,我们对于查一些资源泄漏等问题,更方便了。


原网址: 访问
创建于: 2021-10-22 16:52:18
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论