这里的指多线程意思是在Application阶段的多线程(请参阅<<Real Time Rendering>>3rd对一个实时3d程序处理阶段的划分:Application-Geometry-Rasterization)。本文在Application阶段展开探讨多线程协作,并提出可行方案。
首先,我们回顾一下多线程的应用场景。在一个围绕业务展开的应用程序内,多线程应用场景可以是:界面线程——数据处理线程;界面线程负责响应用户输入,并根据输入向数据处理线程发出请求,接着界面线程会提示进度条并保持界面的及时响应(例如响应用户的取消操作),直到等待数据处理线程的完成。界面线程——数据处理线程二者交互如图:
由上图可知,这是一种等待(同步)机制多线程协作,界面线程发出请求后,需要等待数据处理线程的响应(在保持界面响应的同时)。这种等待可能是一个事件、消息或者一个标志位;但无论如何,界面线程如果得不到数据处理线程的响应,是不会进入下一个处理阶段的,这是由程序的业务逻辑决定的,例如,使用photoshop打开大文件,你不可能在文件还没完全打开的情况下编辑文件,一定要等待文件打开完毕,才能够进行下一步操作。
好了,回到实时渲染3d应用程序上,多线程可以应用在多个方面,例如View Frustum Culling、Collision Detection、Intersection Test、数据结构的操作(manipulate)等。在多线程下,View Frustum Culling可实现为把空间划分多个子空间,并行剔除(当然,运用良好的数据结构可以进行高效的剔除而无需并行剔除,例如kdtree);同样,Intersection Test也可以同时对多个目标进行检测。
多线程如此有用,那么,实时渲染下,我们应该用什么机制组织多线程之间的协作呢?显然,等待机制多线程协作在实时渲染下是不适用的,原因有两:第一,等待有系统开销的,例如线程上下文切换,线程状态转换等,这些开销往往是毫秒级别的,与渲染一帧的时间相当,也就是说等待机制最坏情况下会导致帧速减半;第二,等待机制在实时渲染里就是木桶效应的代名词,导致每一帧Application阶段耗时等于计算时间最长的线程的耗时;综合上述两个原因,等待机制多线程协可导致多线程效率不如单线程。
于是,我在这里,引入另一种多线程协作方式:无等待多线程协作。具体如图:
请注意辅助线程蓝色框,这个步骤是主线程主动获取辅助线程的计算结果,获取结果的时机完全掌握在主线程手上,这样做的好处就是无需等待辅助线程。由于获取结果的时机掌握在主线程手上,所以获取结果时,辅助线程有可能还没有计算完毕,这时,辅助线程可以返回上一次的计算结果,为什么可以这样做?这是由实时渲染的性质决定的,原因请继续往下看。
在实时渲染情况下,Application阶段主要是计算场景内物体在经历dt之后,应出现在什么位置,计算结果将保存为转换矩阵,输入到下一阶段即Geometry 阶段,由GPU根据转换矩阵对物体进行转换后输出到屏幕。例如,一辆以100km/h行驶的汽车,在t0时位置在p0,表现这个状态的帧为f0,由于渲染f0需要时间dt,所以在渲染下一帧f1时,我们要先计算出p1=p0+dtV,在Direct3D里表示为p1=p0*Mt,这里的Mt就是转换矩阵(由dt决定),它的作用就是把物体从p0转换到p1这个位置上(为什么?因为经历了dt这么长时间啊)。在高性能的设备上,渲染一帧的时间很短,即dt很短(dt由Application-Geometry-Rasterization阶段最慢的阶段决定,这3个阶段合称渲染流水线),所以帧速高,帧速可以表示为1/dt,在较差的设备上,dt比较长,所以帧速较慢,但无论帧速如何,都不影响物体位置的变化,因为物体位置变化由我们的公式决定(这里是位移公式)。
由此得出,我们以dt为间隔渲染一帧,又或者以dt*2为间隔才渲染一帧都是一个可接受结果。因此,如果在dt是由Application阶段决定(也就是说瓶颈在CPU)情况下,我们假设辅助线程的计算耗时为主线程的2倍,主线程与辅助线程各自负责计算一辆都以100km/h行驶的汽车的位移情况,这时发生什么现象呢?如果主线程以100fps的速度渲染着汽车,那么辅助线程的汽车就是以50fps的速度刷新着它的位移,但实质上,这辆车还是以100fps的速度渲染,只不过是每隔2帧才更新一次转换矩阵。
这有什么好处?
首先,由于主线程的渲染速度没有被辅助线程拖累,所以用户移动视点时能得到最及时的响应,保证了用户体验;
其次,假设主线程负责计算用户视点附近物体的位移状况而辅助线程负责计算远处物体的位移状况,那么,计算远处的一个异常复杂的连锁爆炸的众多物体运动轨迹可能只能以30fps或者15fps的数度更新,但这都不会影响到近处物体与用户的互动的响应速度。
最后,其实这种多线程协作方式是一个LOD系统,设想一下,假设我们设定远离视点的物体更新速度减半,即在接收数据时判断累积的dt是否大于某阈值,只有当大于阈值时才计算位移,这样做即节省运算资源,又能保证帧速。
以上观点目前仅存在于理论阶段,我会在《实时渲染下的多线程协作:实践》一文中提供具体实现。