array 发表于 2008-8-27 13:38:36

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

当前位置:osgDB/DatabasePager.cpp第479行,osgDB:: DatabasePager:: DatabaseThread::run()
这里首先要注意一点,OSG 2.6的版本会自动判断数据加载请求是否已经过期,即,请求从外部程序发出的时间是否为当前帧或者上一帧,如果这个DatabaseRequest请求是很多帧以前就发出的,那么DatabaseThread就不会再处理它了。这种判断方式显然是有利也有弊的,因为必须仔细考虑数据处理线程与主进程的同步问题。更新版本的OSG对此已经有了改动,不再严格地按照“发出时间是否为当前帧或者上一帧”的条件来进行处理。

下一步,如果判断请求加载的文件来自HTTP网络,且已经设置了系统变量OSG_FILE_CACHE,那么“本地文件处理线程”将尝试建立同名的文件缓存路径,并将请求转交给 “网络文件处理线程”的_httpRequestQueue进行处理(516-547行)。

使用DatabaseThread::dpReadRefNodeFile加载文件,我们判定OSG的reaNodeFile函数是线程安全的,因此dpReadRefNodeFile函数的实现与osgDB:: reaNodeFile没有太大差异(585-589行)。

加载的过程可长可短,由于是在数据处理线程中完成的,因此不会影响到主场景的渲染和遍历。加载完毕之后,网络数据还将使用osgDB::writeNodeFile缓存到本地文件(591-615行)。

然后我们尝试获取DatabaseRequest::_groupForAddingLoadedSubgraph的值,对于已经加载的模型而言,这就是把它合并到当前场景时它的父节点。

下一步的工作大致分为下面几个步骤:

1、获取从这个父节点到场景根节点的一条路径,这实际上可以理解为场景遍历时,从根节点一直到新加载的节点之前的遍历路径,其代码为:osg::NodePath nodePath;
osg::NodePathList nodePathList =
groupForAddingLoadedSubgraph->getParentalNodePaths();
if (!nodePathList.empty()) nodePath = nodePathList.front();
nodePath.push_back(groupForAddingLoadedSubgraph.get());2、创建场景对象的预编译访问器DatabasePager::FindCompileableGLObjectsVisitor,这个访问器的主要工作是找到场景树中所有的StateSet渲染属性集以及Drawable几何体对象,并将它们记录到上一日所述的编译映射表DatabaseRequest::_dataToCompileMap中。这样我们就可以在需要的时候调用映射表中的内容,来执行显示列表创建和纹理绑定这两项重要的OpenGL预处理工作。

3、将第一步得到的节点路径压入到访问器中,这样相当于从场景的根节点开始执行遍历。这里这么做的主要目的是,测试新节点合并到场景树之后场景可能产生的变化。这里涉及到一个较少用到的函数NodeVisitor::pushOntoNodePath,代码如下:for(osg::NodePath::iterator nitr = nodePath.begin(); nitr != nodePath.end(); ++nitr)
frov.pushOntoNodePath(*nitr);4、一切顺利的话,现在所有需要预编译的对象(StateSet与Drawable)都已经记录到“图形设备-编译对象”的映射表_dataToCompileMap中了。这里OSG将需要编译的对象复制到每个GraphicsContext图形设备的对应映射像中。由于映射表的每个像值实质上是相同的,建立这个映射表的意义似乎不大;不过它对于以后的扩展(例如,对于不同的GC设备,预编译的对象有区别时)还是有很大用途的。

5、还需要注意的是,无论是否使用了预编译访问器FindCompileableGLObjectsVisitor(可以通过DatabasePager::setDoPreCompile设置是否执行对象的预编译),系统都会自动为每个对象创建k-Dop Tree包围体(前提是开启了文件加载时的KdTree设置,参见osgkdtree例子),它的主要作用是为场景的射线交集检测提供一种更加精确的测试工具。

6、最后,将已经加载完成,不过还在等待预编译的数据加载请求(DatabaseRequest)送入待编译列表_dataToCompileList(或者待合并列表_dataToMergeList,如果不需要预编译的话)。

当前位置:osgDB/DatabasePager.cpp第729行,osgDB:: DatabasePager:: DatabaseThread::run()
对于DatabaseThread线程的解读似乎逐渐到了尾声,那么我们先加快速度,了解一些还有哪些剩余的工作(根据上一日给出的数据流图来看,似乎不剩什么工作了),并且给我们闲置已久的“悬疑列表”再添加一点新的内容(明明还有很多问题沉积在里面)。

首先从待编译列表_dataToCompileList中筛选并清除所有已经过期的数据请求,即那些早在当前帧或上一帧之前就已经发出的请求(729-755行)。
随后,遍历所有的图形窗口设备,检查它们是否注册了线程(除了SingleThreaded单线程模型之外,其他几种线程模型均会为每个图形设备创建一个线程)。如果线程有效的话,则向其中加入一个DatabasePager::CompileOperation对象;否则按照下面的代码自行处理渲染状态集和几何体对象的编译工作:gc->makeCurrent();
_pager->compileAllGLObjects(*(gc->getState()));
gc->releaseContext();熟悉图形编程的读者一定知道,当我们执行任何OpenGL操作之前,都需要先指定一个渲染上下文(Render context)。Win32环境下我们使用wglMakeCurrent来完成这一工作,X11下则是glXMakeCurrent。由于OSG很好地将这些图形设备和渲染上下文操作指令封装在GraphicsContext的子类当中,因此每当我们试图实现指定或释放渲染RC的操作时,只需要执行相应图形窗口的makeCurrent()和releaseContext()函数即可。

至于DatabasePager::compileAllGLObjects函数,通过阅读其中的内容我们可以很快发现,它的工作无非是取出映射表_dataToCompileMap中的所有StateSet和Drawable对象,并依次执行StateSet::compileGLObjects和Drawable::compileGLObjects函数。

有关compileGLObjects函数,以及它的传入参数osg::State的意义,我们会在阅读渲染线程的过程中再做讲解。

而有关DatabasePager::CompileOperation类,根据已有的经验(第四日,第十日)可以知道,它继承自osg::Operation,并重写了operator()操作符,以完成指定的图形操作。但是,有关它被追加到图形设备线程之后,又是如何被执行的,这一点同样会在介绍渲染线程时涉及到。

“待编译列表”中的对象在预编译完成后会转存到“待合并列表”中。

解读成果:
DatabasePager:: DatabaseThread::run。
悬疑列表:
如何调度和实现OSG的多线程机制?什么是“渲染目标实现方式”?如何使用compileGLObjects完成预编译工作?Operation对象在线程中的应用时机是什么?

feiyanddy 发表于 2008-8-27 16:22:55

谢谢Array了,正在拜读!:)

shengrendan2 发表于 2008-9-27 15:33:23

行;
页: [1]
查看完整版本: OSG原创教程:最长的一帧(13)