|
当前位置:osgViewer/ViewerBase.cpp第730行,osgViewer::ViewerBase::renderingTraversals()
这里我们还落下了一个十分重要的变量_endDynamicDrawBlock,在“渲染开启栅栏”和“渲染结束栅栏”的同步代码段之间,有这样一行代码(730行):- if (_endDynamicDrawBlock.valid()) _endDynamicDrawBlock->block();
复制代码 很显然,由于_endDynamicDrawBlock是一个计数阻塞器,因此当且仅当我们执行了指定次数的completed()函数之后,主进程才会被释放,后面的代码才会被继续执行。
那么completed函数是在哪里执行的呢?我们需要结合多处代码来进行分析。首先是ViewerBase::startThreading函数中(337行):- if (_threadingModel==DrawThreadPerContext ||
- _threadingModel==CullThreadPerCameraDrawThreadPerContext)
- {
- ……
- _endDynamicDrawBlock = new osg::EndOfDynamicDrawBlock(
- numViewerDoubleBufferedRenderingOperation);
- ……
- }
- if (numThreadsOnStartBarrier>1)
- {
- _startRenderingBarrier = new osg::BarrierOperation(
- numThreadsOnStartBarrier, osg::BarrierOperation::NO_OPERATION);
- }
- if (numThreadsOnEndBarrier>1)
- {
- _endRenderingDispatchBarrier = new osg::BarrierOperation(
- numThreadsOnEndBarrier, osg::BarrierOperation::NO_OPERATION);
- }
- ……
- for(citr = contexts.begin(); citr != contexts.end(); ++citr, ++processNum)
- {
- ……
- osg::GraphicsContext* gc = (*citr);
- gc->getState()->setDynamicObjectRenderingCompletedCallback(
- _endDynamicDrawBlock.get());
- ……
- }
复制代码 这里列出了相当长的一段代码,不过其中却透露了很多信息。例如_startRenderingBarrier和_endRenderingDispatchBarrier的初始化就是在这里完成的,不过我们更希望了解的是动态绘制阻塞器_endDynamicDrawBlock的初始化过程。注意,_endDynamicDrawBlock只有在DrawThreadPerContext和CullThreadPerCameraDrawThreadPerContext两种模式下会被初始化,这是因为只有这两种模式下才会出现上一帧渲染未结束而下一帧开始的情况。而正如我们在以往的教程中所了解的,如果一个对象需要在运行时被修改,那么需要设置它为动态对象,即:- object->setDataVariance( DYNAMIC );
复制代码 否则将可能出现渲染中的数据被新一帧的用户回调更改的情形,并因此造成系统崩溃或者各种无法预知的错误。由于单线程和CullDrawThreadPerContext模型不会出现渲染过程与用户更新过程交叠的情形,因此不必考虑这一操作。
动态绘制阻塞器_endDynamicDrawBlock的作用就是避免DYNAMIC对象的运行时更改影响后台渲染的工具。在初始化时我们需要设置它的最大计数值,当completed函数的执行次数达到这一数值时,将释放被阻塞的线程,也就是我们的主进程。这一最大计数值正是场景中摄像机渲染器(Renderer)的个数,为什么呢?让我们转到RenderLeaf::render函数,其中有这样一段代码(osgUtil/RenderLeaf.cpp,77行):- if (_dynamic)
- state.decrementDynamicObjectCount();
复制代码 而State::decrementDynamicObjectCount函数的内容为(osg/State,1013行):- {
- --_dynamicObjectCount;
- if (_dynamicObjectCount==0 && _completeDynamicObjectRenderingCallback.valid())
- _completeDynamicObjectRenderingCallback->completed(this);
- }
复制代码 这里State ::_dynamicObjectCount 变量表示当前状态机中仍在等待渲染的所有动态对象的数目,它是通过RenderStage::computeNumberOfDynamicRenderLeaves函数计算的。
由此可见,每当一个被设置为“动态”的渲染叶(“动态”的属性事实上是从Node、Drawable或者StateSet对象继承来的)都会在渲染结束时执行动态对象计数的“减一”操作;而每当OSG状态机中的动态对象计数为0时,相应的动态对象渲染回调(这个参数实际上就指向_endDynamicDrawBlock)将执行completed操作。由于每个摄像机的渲染器都会根据自己的实际情况重新刷新状态机State的内容,并因而在全部动态对象的渲染结束时执行一次completed函数。因此,最终所有n个摄像机的内容都渲染完毕时,completed函数也将被执行n次,并进而解开了计数阻塞器的阻塞,释放主进程,使之继续后面的代码,开始下一帧的运行。
因此,设置场景中的节点、几何体或者渲染状态为DYNAMIC对象时,将保证这些对象的渲染结束之前,不会开始下一帧的用户更新工作,也就保证用户对数据的修改操作不会对本次的渲染结果和系统的稳定性产生影响。
当前位置:osgViewer/ViewerBase.cpp第361行,osgViewer::ViewerBase::startThreading()
解决了动态对象渲染阻塞器的问题,现在我们可以转手去面对_startRenderingBarrier和 _endRenderingDispatchBarrier这对孪生兄弟,还有多线程渲染处理的问题了。虽然我们已经解释了有关渲染栅栏的原理以及它们的初始化代码。但是悬而未决的问题实质上还有很多:
- 1、图形设备线程和摄像机线程,它们是如何工作的呢?预编译、筛选、绘制,这些事情有的线程要做,有的则不必做,如何区分它们呢?在“悬疑列表”中搁置的问题“Operation对象在线程中的应用时机”,是否就是线程工作的关键所在呢?
- 2、渲染开启栅栏和结束栅栏都已经有了,可是它们该如何去阻塞各个图形线程呢?况且我们怎样定义何时算是渲染开始,何时算是结束呢?
- 3、除了对动态对象渲染的处理之外,CullDrawThreadPerContext,DrawThreadPerContext和CullThreadPerCameraDrawThreadPerContext三种线程模型之间,还有没有别的不同点?又如何去评判它们的优劣呢?
那么就让我们在下一日尝试去揭开OSG图形渲染线程的面纱吧。
悬疑列表:
Operation对象在线程中的应用时机是什么? |
|