array 发表于 2008-8-19 13:33:08

OSG原创教程:最长的一帧(6)

当前位置:osgViewer/Viewer.cpp第510行,osgViewer:: Viewer::advance()
好的,现在我们终于正式进入仿真循环当中了,之前的realize函数虽然十分重要,但它实际上是循环运行前的准备工作。而从这一日开始介绍的advance,eventTraversal,updateTraversal和renderingTraversals函数,才是真正的一帧的组成部分。

advance函数的工作内容如下:

[*]1、获取上一次记录的参考时间(Reference Time);
[*]2、根据当前时刻,重新记录参考时间,并因此得到两次记录之间的差值,即一帧经历的时间;
[*]3、记录已经经过的帧数;
[*]4、有的时候我们需要将帧速率,参考时间等内容予以记录并显示给用户,此时需要通过ViewerBase::getStats函数获得osg::Stats对象,用以进行帧状态的保存和显示;
[*]5、如果需要的话,使用Referenced::getDeleteHandler()来处理osg::Referenced对象被弃用之后的删除工作。


仿真循环运行的参考时间,总时间和总帧数都是由osg::FrameStamp变量_frameStamp来处理的,如果用户程序需要获取这些信息的话,也可以通过读取这个变量的成员函数来实现。当然,使用Viewer中的osg::Stats变量_stats也是可以的,缺省情况下,这个变量会忠实地记录当前帧以及之前的24帧的每帧用时,事件遍历用时,更新遍历用时,以及渲染遍历用时信息。如果我们想获得更多的历史数据,抑或对于频繁的记录操作感到厌烦,可以在开始仿真循环之前执行ViewerBase::setStats函数,重新设置这个记录器的参数,或者简单地将其置为NULL。

一切顺利!不过在结束短暂的advance之旅之前,我们还有一个问题需要解决,就是那个看起来有点让人不解的getDeleteHandler:osg::Referenced::getDeleteHandler()->flush();
osg::Referenced::getDeleteHandler()->setFrameNumber(_frameStamp->getFrameNumber());简单说来,它的工作是收集所有已经弃用的OSG场景对象,并在需要的时候(例如advance函数代码的相应部分)执行osg:: DeleteHandler::flush,将它们统一删除。

这里所说的“弃用”,与我们非常熟悉的osg::ref_ptr智能指针是密切相关的。我们已经知道,ref_ptr采用内存引用计数的方式,当一个场景对象(通常是Node节点)链接到根节点或者其他节点时,它的引用计数加一,这一动作是通过ref_ptr::ref()函数实现的;如果它被剔除出节点,那么它的引用计数减一,执行这一工作的函数是ref_ptr::unref()。unref函数的另一个重要任务是检查对象的引用计数值是否到达零,如果已经没有被其它对象所引用,那么称这个对象被“弃用”,它应当被立即删除,以释放相应的内存空间,避免泄露。

C++中最通用的删除对象的方法是delete,OSG的智能指针本质上也是采用这种方式来释放对象的,不过由于OSG采用多线程更新/渲染的方式(这一点我们会在后面的日子中详细介绍),这样做可能带来会某些隐患,想象这样一种情况:1、场景某个的节点负责显示某种图形,它的工作一直很正常;
2、我们采用DrawThreadPerContext或者CullThreadPerCameraDrawThreadPerContext线程模型,根据前一日中我们所知的,这两种模式中存在“上次的渲染工作与下次的更新工作交叠”这一情形。
3、假设我们在更新工作中立即将这个节点删除,而上次渲染工作可能正要将这个节点中的数据送往OpenGL图形渲染管线,那么灾难就发生了……看到这里,读者您一定已经想到了一种解决方案。对,就是在渲染后台也使用ref_ptr来引用(ref)图形节点,然后在渲染结束取消引用(unref),这样不就可以避免无谓的牺牲了吗?也省却用户的很多麻烦。

说得有道理,不过这其中恐怕忽视了一个核心的问题:渲染效率。是的,假设我们要渲染成千上万个这样的几何体节点(这对您来说也许简直是家常便饭),如果每个节点的渲染都要多执行一次ref/unref的话,效率的损失将是无法被忽略的。事实上经过测算,CPU时间的流失大概可以达到6%,对于一个实时渲染系统来说,这的确值得斟酌。

因此,OSG的新版本中提出了DeleteHandler的概念,也就是“垃圾收集”,把那些引用计数已经为零的对象统一收集起来,确保它们不会再被渲染线程用到之后,再在适当的地方予以释放。DeleteHandler有一个重要的参数_numFramesToRetainObjects,它的意义是,垃圾对象被收集之后,再经过多少帧(默认设置是2),方予以释放。因此,OSG的垃圾收集器同样需要使用DeleteHandler::setFrameNumber来记录当前的帧数。

这个概念提出的时间并不长,也许还需要一段时间的测试,也许会有更好的方案来替代它。目前,OSG的发行版本仍然采用第一种方式,也就是渲染后台采用ref_ptr引用计数的方式来避免删除对象造成的问题;如果您想要尝试使用和帮助调试DeleteHandler的话,可以在自己的程序中(main函数之前)加入:#undef OSGUTIL_RENDERBACKEND_USE_REF_PTR以请求使用DeleteHandler。

当前位置:osgViewer/Viewer.cpp第549行,osgViewer::Viewer::eventTraversal()
在开始了解OSG事件遍历的过程之前,也许有必要先总结一下OSG视景器、摄像机与场景的关系。如下图所示:
见附图1

视景器包括几个最主要的组件:漫游器_cameraManipulator,用于实现交互式的场景漫游;事件处理器组_eventHandlers,负责处理视景器的事件队列_eventQueue,主要是键盘/鼠标等事件的处理;场景_scene,它包括视景器所对应的场景图形根节点,以及用于提高节点和图像数据处理速度的两个分页数据库;摄像机_camera和_slaves,前者为场景的主摄像机,后者为从摄像机组,不过OSG并没有规定一定要使用主摄像机来显示场景,它的更重要的作用是为OSG世界矩阵的计算提供依据。

摄像机是OSG视图显示的核心器件,没有摄像机就没有办法将场景图形的实景展现给用户。它包括:

1、视口(Viewport),它指示了摄像机显示窗口的位置和尺寸。

2、图形上下文(GraphicsContext),通常这也就是平台相关的图形显示窗口(即GraphicsWindow,对于Win32系统而言,它实际上是通过CreateWindowEx这个熟悉的API来创建的),不过也可能是离屏渲染的设备(例如PixelBufferWin32)。

图形窗口的另一个任务是及时把系统和用户交互的事件反馈到事件处理器组中去,观察Win32平台下的窗口设备GraphicsWindowWin32中的handleNativeWindowingEvent函数和它的传入参数,hwnd,msg,lParam,wParam……没错,相信您已经找到熟悉的感觉了。OSG所处理的事件正是来源于Win32 SDK编程中常见的窗口消息。如果您正好有些新的想法想要实践(例如,捕捉WM_HOTKEY系统热键消息,并传递给OSG的事件队列),不妨试一试修改相关的内容,并记得把好的建议发送到osg-users。

3、渲染器(GraphicsOperation,更多时候是osgViewer::Renderer),这是整个OSG筛选(CULL)和绘制(DRAW)的关键,它的功能我们会在后面的日子里慢慢展开。
此外,OSG的显示设置工具DisplaySettings也会直接对摄像机的处理工作负责,大部分设置选项都可以传递到摄像机对应的窗口特性(GraphicsContext::Traits)中,并在渲染过程中发挥作用。

好了,闲话少说,下一日,让我们继续旅程。

解读成果:
osg:: DeleteHandler,osgViewer:: Viewer::advance。
悬疑列表:
类变量_cameraWithFocus的意义是什么?如何调度和实现OSG的多线程机制?

flying5 发表于 2008-8-21 08:47:37

好贴,高水平的好贴
堪比MFC深入浅出
感谢楼主
尽快出书《OSG深入浅出》哈

shengrendan2 发表于 2008-9-27 13:13:19

好;
绝;

forest37 发表于 2008-12-2 15:29:07

“我们采用DrawThreadPerContext或者CullThreadPerCameraDrawThreadPerContext线程模型,根据前一日中我们所知的,这两种模式中存在“上次的渲染工作与下次的更新工作交叠”这一情形。"
如果我们不采用这两种模型,会不会在渲染后台也使用ref_ptr来引用(ref)图形节点呢?

array 发表于 2008-12-2 16:02:08

??
“上次的渲染工作与下次的更新工作交叠”,意思就是说上一帧的DRAW与下一帧的APP/CULL重叠。不知您的理解是什么

forest37 发表于 2008-12-2 16:37:44

不好意思,我的表达让您误解了。
我的意思是说如果我们   不采用   DrawThreadPerContext或者CullThreadPerCameraDrawThreadPerContext这两种模型,程序会不会在渲染后台仍然使用ref_ptr来引用(ref)图形节点呢?我认为不会。
或许这是在绘制同样的场景图形下,DrawThreadPerContext模型CULL的时间比单线程CULL的时间长的原因之一

array 发表于 2008-12-2 16:51:08

渲染后台是始终使用ref_ptr来引用图形节点的,或者始终不使用。这里是采用宏来定义的,不会根据线程模型的不同而改变,至少我所依据的2.6版本是这样的
页: [1]
查看完整版本: OSG原创教程:最长的一帧(6)