osg学习osg源码分析-最长的⼀帧第五⽇
第五⽇
当前位置 osgViewer/Viewer.cpp463,osgViewer::Viewer::realize()
下⾯我们再次遍历所有GraphicsContext设备,对于每个GraphicsContext指针gc,判断它是否为GraphicsWindow对象,并执⾏GraphicsWindow::grabFocusIfPointerInWindow函数。阅读GraphicsWindowWin32类(即GraphicsContext的具体实现者)的同名函数可以发现,这个函数不过是负责把⿏标焦点转到当前窗⼝上⽽已。
下⼀步⼯作的代码如下:
osg::Timer::instance()->setStartTick();
setStartTick(osg::Timer::instance()->getStartTick());
setUpThreading();
⾸先调⽤osg::Timer::setStartTick函数,启动OSG内部定时器并开始计时。
随后,Viewer::setStartTick函数的⼯作是到当前视景器和所有GraphicsContext设备的事件队列_eventQueue,并设定它们的启动时刻为当前时间。
下⼀⾏是调⽤ViewerBase::setUpThreading函数……设置线程,对于⼀向以多线程渲染⽽闻名的OSG⽽⾔,这⼀定是个值得深究的话题。
当前位置 osgViewer/ViewerBase.cpp122osgViewer::ViewerBase:: setUpThreading()
OSG的视景器包括四种线程模型,可以使⽤setThreadingModel进⾏设置,不同的线程模型在仿真循环运⾏时将表现出不同的渲染效率和线程控制特性。通常⽽⾔,这四种线程的特性如下:
SingleThreaded:单线程模型。OSG不会创建任何新线程来完成场景的筛选和渲染,因⽽也不会对渲染效率的提⾼有任何助益。它适合任何配置下使⽤。
CullDrawThreadPerContext:OSG将为每⼀个图形设备上下⽂(GraphicsContext)创建⼀个图形线程,以实现并⾏的渲染⼯作。如果有多个CPU的话,那么系统将尝试把线程分别放在不同的CPU上运⾏,不过每⼀帧结束前都会强制同步所有的线程。
DrawThreadPerContext:这⼀线程模型同样会为每个GraphicsContext创建线程,并分配到不同的CPU上。⼗分值得注意的是,这种模式会在当前帧的所有线程完成⼯作之前,开始下⼀帧。
CullThreadPerCameraDrawThreadPerContext:这⼀线程模型将为每个GraphicsContext和每个摄像机创建线程,这种模式同样不会等待前⼀次的渲染结束,⽽是返回仿真循环并再次开始执⾏frame函数。如果您使⽤四核甚⾄更⾼的系统配置,那么使⽤这⼀线程模型将最⼤限度地发挥多CPU的处理能⼒。
与DrawThreadPerContext和CullThreadPerCameraDrawThreadPerContext这两种同样可以⽤于多CPU系统,且相对更有效率的线程模型相⽐,CullDrawThreadPerContext的应⽤范围⽐较有限;⽽SingleThreaded模式在单核以及配置较低的系统上运⾏稳定。
这些话长篇⼤论地说出来似乎令⼈满腹疑窦:OSG为什么要为每个GraphicsContext设备配置⼀个线程?为什么⼜要为每个摄像机配置⼀个线程?线程的同步是怎么实现的?线程与CPU的关系⼜是怎么处理的?OSG⼊门书籍中常说的更新(Update)/筛选(Cull)/绘制(Draw)三线程⼜是在那⾥体现的?为什么……
天哪,这么多问题我们都要解读吗?是的,绝对要解读,不管花费多少时间!OSG的学习是为了实际的应⽤,但是只有真正理解了它的运⾏机制,才能够最有效地把这个愈加著名的实时场景渲染软件⽤好。
但是有些事情是急不来的,从frame函数的源代码中可以⼤致推测出来,场景的筛选和绘制⼯作是由Vi
ewerBase::renderingTraversals函数来完成的。相应的,很多线程的调度和同步⼯作也是在这个函数中完成的,那么就让我们把问题留到那个时候吧。不过不妨先透露⼀点信息:第四⽇中我们提到的渲染器(Renderer)类,事实上也是与OSG的渲染线程密切相关的,因为筛选和绘制的⼯作就是由它来具体负责!好的,遗留的问题可以说暂时得到了解答,不过新的问题⼜出现了,⽽且任务看起来更为艰巨,继续努⼒好了。
数据库学习入门书籍线程相关的问题留待后⾯解决,不过还是让我们先通读⼀下setUpThreading函数的代码也⽆妨。它的⼯作主要是处理单线程(SingleThreaded)模式下,多处理器系统的数据线程分配⽅式。
听起来很深奥,不过实际上这⾥没有多么复杂。在现阶段,如果采⽤单线程模式的话,OSG系统将使⽤CPU0来处理⽤户更新、筛选和渲染等⼀切事务,⽽使⽤CPU1来处理场景的两个分页数据库(DatabasePager)线程(它们分别⽤于处理本地和⽹络上的场景数据)。
这⾥还出现了⼀个Viewer::getScenes函数(osgViewer/Viewer.cpp,141⾏),它的作⽤是获取当前视景器对应的osgViewer::Scene对象,也就是场景。⼀个场景包括了唯⼀的场景图形根节点,分页数据库(DatabasePager),以及分页图像库(ImagePager)。Viewer视景器对象通常只包括⼀个Scene场景,⽽CompositeViewer复合视景器则可能包括多个场景对象.
如果系统采⽤了SingleThreaded之外的其它线程模型,那么setUpThreading函数将⾃动执⾏ViewerBa
se::startThreading——多线程渲染的最重要函数之⼀,这个函数将在我们追踪到renderingTraversals函数的时候重新进⾏解析。
当前位置 osgViewer/Viewer.cpp486osgViewer::Viewer::realize()
好了,如果您还没有忘记我们来⾃何⽅的话,请回到realize函数,现在这个函数的执⾏已经接近了尾声,不过我们⼜遇到了⼀个问题:编译上下⽂(也就是Compile Contexts,暂时就这样翻译吧)?如果要启⽤它的话并不困难,只需要在调⽤realize之前执⾏:
osg::DisplaySettings::instance()->setCompileContextsHint(true);
随后,正如您在realize函数的491-503⾏之间看到的,系统将设法遍历所有可能的GraphicsContext设备,针对它们分别再各⾃添加⼀个新的GraphicsContext设备(也就是说,如果系统中已经有了数个图形上下⽂,那么现在⼜将新增同样数量的图形上下⽂与之对应),所⽤的函数为GraphicsContext::getOrCreateCompileContext。这之后,分别执⾏了创建图形线程,设置CPU依赖性,以及启动图形线程的⼯作,具体的实现内容可以暂时忽略。
观察getOrCreateCompileContext函数的内容,很快我们就可以发现其中的重点:这些新增的GraphicsContext对象使⽤了pBuffer的特性,并与对应的已有对象共享同⼀个图形上下⽂(Traits::shar
edContext特性)。事实上,这是OSG利⽤OpenGL的像素缓存(Pixel Buffer)技术,为图形上下⽂的后台编译提供的⼀种新的解决⽅案。这样不仅可以提⾼图形刷新的速度,还可以⽅便⽤户为某⼀特定的GraphicsContext设备添加特殊的处理动作,⽅法是使⽤osg::GraphicsContext::getCompileContext获取后台图形上下⽂,再使⽤GraphicsContext::add函数向其中追加osg::Operation对象,类似的例⼦可以参看osgterrain。
对了,在结束这⼀⽇的旅途之前,还要提⽰⼀句:“编译上下⽂”这⼀功能在Windows的实现尚有问题,⽬前可能会造成系统的崩溃(不要⼤失所望呀^_^)。
解读成果
线程模型,osgViewer::Viewer::realize。
悬疑列表
类变量_cameraWithFocus的意义是什么?如何调度和实现OSG的多线程机
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论