游戏引擎是如何调度渲染和逻辑代码的
前排跑题警告,想借着这个题交流一下最近捣鼓的一套渲染器的接口流程,而不是单纯局限于Unity的时钟处理。下面提到的这套接口流程还没有经过任何验证,所以如果有大佬能指出缺陷,将会是十分珍贵的。
我的目标就是使用原生的DirectX 12开发独立渲染模块,而这个模块要做到的是可以打包成动态库给其他游戏引擎使用,因此要做到本身只做纯渲染的工作,输入和输出都相对干净,同时还要尽可能绕开C++标准中没有的部分,防止踩坑,此为前提。
首先,我将整体框架解耦成:窗口,渲染器两个部分,窗口可以是最简单的Winmain + WNDCLASS,也可以是Unity或者Unreal那种丰富到极致的花里胡哨的GUI,而我认为这些窗口无论怎么千奇百怪也离不开Backbuffer, Swapchain等等,因此,我把窗口部分向渲染部分传递的数据整理下来:
有些操作是不同窗口不同做法的,这就意味着这些操作的事件需要经过窗口层传递到渲染层,这就多开一个单独存储函数指针的类型:
因为调用的主动权一直是在窗口层,而渲染层只有被调用权,因此窗口层将会持有渲染层的“引用”,这个引用将会拆解成一个void*指向类的地址,和一个存储所有渲染层应有的逻辑的函数指针列表:
这里Initialize也就是包括了整个渲染器的构造,并且返回指针,Dispose自然就是析构函数,其他两个则是窗口大小改变和每帧的绘制函数,最后,渲染层的动态库将会提供一个生成这个IRendererBase的裸函数,拼上最后一块砖:
这样如果要解耦合渲染器部分,比如打包DLL,基本只需要将GetRendererFunction函数标记为DLL导出格式,窗口层的设计就比较随意,只要保证让渲染器在窗口初始化之后再构造,在窗口销毁之前被销毁,并且每帧调用一下,就可以了。
在最原始的windows app里(忽略Debug部分)调用过程就几行,其中D3DApp是窗口:
Run内部的逻辑比较简单粗暴,就是while循环调用Draw,外加一些更新时间等操作。因为渲染器是一个多线程并行的多帧异步渲染模式,因此Draw函数逻辑很简单并不会有什么开销,唯一的阻塞就是需要等待两帧之前的GPU的渲染结果,当然这也是没有办法的事情。
通过这套系统,把可能的逻辑和渲染层的沟通都涵盖住了,如果逻辑需要更新,则通过DataPack传递进去,渲染器因为只负责把结果输出到屏幕,所以可以不设计任何数据上和逻辑上的回调。