array 发表于 2008-8-26 13:51:12

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

当前位置:osgDB/DatabasePager.cpp第407行,osgDB:: DatabasePager:: DatabaseThread::run()
在讲解DatabaseThread线程之前,我们理应先仔细考虑一下,OSG的分页数据库应该使用单独的线程来处理什么:

[*]1、删除过期的场景数据:这一步工作当然也可以在仿真循环中进行,但是这样做很可能会造成场景渲染的延迟,我们采用线程来处理场景数据的理由也正是因为如此。过期对象的统一删除工作在这里完成,而更新遍历则负责将检索到的对象送入相应的过期对象列表。

[*]2、获取新的数据加载请求:请求加载的可能是新的数据信息,也可能是已有的场景数据(曾经从“当前页面”中去除,更新又回到“当前页面”中);可能是本地的数据文件,也可能来自网络,并需要把下载的数据缓存在本地磁盘上。这些都需要在线程中一一加以判断。

[*]3、编译加载的数据:有些数据如果提前进行编译可以有效地提升效率,例如为几何体数据创建显示列表(Display List),以及将纹理对象提前加载到纹理内存;虽然OSG同样可以在渲染时根据用户需要执行这些工作,但是那样势必会造成帧的延迟,对于大型场景的加载来说这种延迟将更为严重。因此预编译加载的数据是很有必要的。在数据处理线程执行预编译工作当然不为过,但是如果系统配置足够高级的话,也可以选择由图形设备线程(GraphicsContext::getGraphicsThread)来完成这些原属于它们的工作。

[*]4、将加载的数据合并至场景图形:直接由线程来完成这一工作显然是不合适的,因为我们不知道当DatabaseThread线程试图操作场景中的节点时,OSG的渲染器在做些什么。最好的方法是将读入的数据先保存在一个列表中,并且由仿真循环负责获取和执行合并新节点的操作。


那么,我们就得到了一个也许可行的数据流图,如下所示:
附图1

左侧的图框表示数据的检索和输入,中间的白色图框表示用于数据存储的内存空间,而右边的图框表示存储数据的输出。此外,蓝绿色图框表示可以在DatabaseThread线程中完成的工作,而橙色图框表示由线程之外的函数完成的工作。

这幅图中事实上已经标示出了DatabasePager中的几个重要成员变量。不过在认识它们之前,我们还需要了解一下DatabasePager类所定义的各种数据结构:

[*]1、DatabasePager:: DatabaseThread类:这是分页数据库的核心处理线程,它负责实现场景元素的定期清理,加载以及合并工作;但是让它一直处于检查各个数据列表的循环状态,这未免太过耗费系统资源。因此,这个线程在平常状态下应当被阻塞,需要时再予以唤醒。

[*]2、DatabasePager:: DatabaseRequest结构体:这个结构体保存了用户的单个数据请求,包括数据文件名,请求时间,数据加载后存入的节点,以及要进行合并的父节点等;除此之外还有一个重要的编译映射表_dataToCompileMap,这个映射表负责保存图形设备ID与编译对象(几何体显示列表,纹理等)的映射关系。

[*]3、DatabasePager::RequestQueue结构体:它负责保存和管理一个“数据请求列表”_requestList,也就是由DatabaseRequest对象组成的向量组,除此之外还负责对列表中的数据按请求时间排序。上图中所示的_dataToCompileList和_dataToMergeList实际上都是RequestQueue类型的对象,不过它们所保存的“请求列表”事实上是已经完成加载的“待编译/待合并列表”了。

[*]4、DatabasePager::ReadQueue结构体:这个结构体继承自RequestQueue,不过还增加了一个“弃用对象列表”_childrenToDeleteList,也就是osg::Object对象组成的向量组。它是数据处理线程中最重要的对象之一,除了可以随时向两个列表里追加数据请求和弃用对象之外,这个结构体还包括了一个updateBlock函数,负责阻塞或者放行DatabaseThread线程,其根据是:列表中是否存在新的数据请求或弃用对象需要处理,以及用户是否通过函数设置暂时不要启用线程(DatabasePager ::setDatabasePagerThreadPause)。


OSG 2.6版本的DatabasePager中缺省创建了两个数据处理线程,均保存在线程列表_databaseThreads中,这两个线程分别负责处理来自本地文件的数据队列_fileRequestQueue和来自HTTP站点的网络数据队列_httpRequestQueue(这两个队列均为ReadQueue对象)。来自DatabaseThread线程之外的文件加载请求被本地文件处理线程所接收;如果从文件名(DatabaseRequest::_fileName)判断出是来自网络的文件的话,则转交给网络数据处理线程。

来自网络的数据也可以被缓存到本地,方法是设置系统变量OSG_FILE_CACHE为本地路径。

当前位置:osgDB/DatabasePager.cpp第451行,osgDB:: DatabasePager:: DatabaseThread::run()
现在我们可以进入线程循环体内部浏览了。每次循环开始时,数据处理线程都被自动阻塞,避免无谓的系统消耗;直到updateBlock函数在外部被执行才会放行,继续下面的代码。
updateBlock函数可能在以下几种情形下被执行:

[*]1、ReadQueue对象中的“数据请求列表”被修改,例如新的数据加载请求被传入,请求被取出,列表被重置。
[*]2、ReadQueue对象中的“弃用对象列表” 被修改,例如有新的过期对象被送入,对象被删除,列表被重置。
[*]3、执行了DatabasePager ::setDatabasePagerThreadPause函数,当线程被重新启动时,会自动检查线程是否应当被唤醒。


这之后是过期数据的删除工作,即取出_childrenToDeleteList中的所有对象,并安全地将它们析构(462-471行)。

随后,使用DatabasePager::ReadQueue::takeFirst函数,从当前线程对应的ReadQueue对象(_fileRequestQueue或_httpRequestQueue)的队列中取出并清除第一个数据加载请求(DatabaseRequest),而下一步的工作就是处理这个请求了。

解读成果:
DatabasePager数据流,重要变量。
悬疑列表:
如何调度和实现OSG的多线程机制?什么是“渲染目标实现方式”?

[ 本帖最后由 array 于 2008-8-26 13:52 编辑 ]

shengrendan2 发表于 2008-9-27 15:04:42

我坐沙发~~爽
页: [1]
查看完整版本: OSG原创教程:最长的一帧(12)