终于快结束了,再接再厉。GOGOGO!
对于剩下的两个线程模型,
一,默认的线程模式是DrawThreadPerContext
其实,前面有些已经跟踪过了,再一边抄一边调试,熟悉下,一遍肯定记不住。
如果ViewerBase类的成员函数suggestBestThreadingModel(),没有找到适合当前计算机系统的线程模式的话,将自动采用这一模式来完成渲染的工作。
DrawThreadPerContext模式没有设置渲染启动栅栏和结束栅栏。因此,这一渲染模式下将不会在每一帧对场景的筛选和绘制工作进行同步,且用户的更新动作将有可能在某些线程的渲染动作还未结束时即开始运行。
DrawThreadPerContext模式下根据场景中摄像机的数量设置_endDynamicDrawBlock变量的值,这个阻塞器用于在每个渲染器都渲染完毕之前阻塞主进程的运行,以免用户对数据的更新动作于动态对象的渲染动作产生冲突。
这个numViewerDoubleBufferedRenderingOperation是根据摄像机获得的。
初始为0
接下来即DrawThreadPerContext模式下需要执行的任务列表。由于也走ViewerBase::startThreading函数,所以很多可以在不同模式下复用的流程,只是根据变量进入不同的分支。
回顾下第29天CullDrawThreadPerContext,
需要的是5个
1,startRenderingBarrier
2,osg::RunOperations
3,swapReadyBarrier
4,swapOp
5,endRenderingDispatchBarrier
还是比较全面的。处处栅栏。
而在DrawThreadPerContext,三个流程
1,osg::RunOperations
2,swapReadyBarrier
3,swapOp
也是这三步,但是在第一步osg::RunOperations,只有draw(),没有cull()
那么cull()在哪里呢?
这里和单线程模式一样,场景筛选工作在ViewerBase::renderingTraversals函数实现。Render::getGrahphicsThreadDoesCull()只有在CullDrawThreadPerContext或者单线程模式下为true,而摄像机线程getCameraThread只有在最后一种多线程渲染模式下才会被创建(CullThreadPerCameraDrawThreadPerContext)。因此,此处的Renderer::Cull()函数将被执行。
这样一来,场景的筛选工作将在每一帧当中仅执行一次,而绘制工作则交给GC线程来完成。这样并不会造成筛选和绘制数据之间的冲突,因为如上图可知,在Renderer::cull最后向绘制队列_drawQueue中添加一个已完成筛选的场景视图对象(SceneView)。
而Renderer::draw函数一开始就尝试从这个队列中取出一个数据,(takeFront函数),并清空它在队列中的位置。因此,如果新的线程绘制工作提前到来的话,由于场景筛选的函数还没有把SceneView传入到_drawQueue队列中,因此,这次多余的绘制动作将自动宣告结束。
仍然假设DrawThreadPerContext有3个条件不一的GC线程,流程如下图所示。
可见
1,主进程中的筛选一旦结束,县城所控制的绘制马上开始。
2,由于存在交换缓存栅栏swapReadyBarrier,各个图形设备的交换缓存工作依然是统一执行。
3.在DrawThreadPerContext模式不存在渲染启动和渲染结束栅栏,因此,主进程执行完场景的筛选之后就可以继续执行,进而开始新一帧的数据更新工作。为了避免GC线程的绘制工作和场景中需要绘制的数据发生变化,从而引起崩溃,则需要动态对象阻塞器_endDynamicDrawBlock(需要把变度对象设置为setDataVariance(Object::DYNAMIC,在CullThreadPerCameraDrawThreadPerContext也需要这样设置).
二,在CullThreadPerCameraDrawThreadPerContext模式
这次先上图
与前面类比可知,是摄像机线程筛选后GC线程立即绘制。
从上图可以看出,CullThreadPerCameraDrawThreadPerContext模式下,建立了多个用于场景筛选的摄像机线程;以及用于场景绘制的多个GC线程。这种模式也提供了动态对象阻塞器_endDynamicDrawBlock,以免更新动作影响到场景渲染。
其中,各个GC线程的任务列表与DrawThreadPerContext模式相同。
1,osg::RunOperations任务负责场景绘制。
2,swapReadyBarrier任务的作用是在交换双缓存之前对所有的GC线程执行一次同步。
3,swapOp任务用于执行绘制后的双缓存交换操作。
摄像机线程的数目与场景摄像机数目相同,其任务列表如下:
1,_startRenderingBarrier:筛选启动栅栏,其强度是场景摄像机数目+1。
因此,在renderingTraversal函数中,将会通过阻塞主线程,对场景的筛选线程(即各个摄像机执行一次同步)
2,osgViewer::Renderer,它也可以作为一个操作任务存在。当线程中执行到这个任务时,回转而执行Renderer::operator()(osg::Object* object)函数,并在其中执行Renderer::Cull函数,完成场景的筛选。
最后,电子书介绍了下cpu的亲缘性,如下图,4cpu,3摄像机线程和3GC线程
。
分配cpu的顺序是先GC线程,再摄像机线程,即GC1->N,再摄像机线程1->N.从cpu1->4->1-....>4
即,除了单线程外,平均分配CPU,
使用的参数是
OK,电子书全部调试了一遍,哈哈。