上节总结
前几天我们大体上介绍完成了osg的事件循环的介绍,总结一下osg的时间循环主要就是得到平台(windows)的所有消息,并遍历所有的node的eventCallback,并对他们进行处理。接下来我们就要进入osg的另一个维持生命的循环---更新循环。
更新循环
OSG 更新循环的作用与事件回调有类似之处:由专门的访问器对象_updateVisitor 的负 责场景图形更新遍历;所有的节点和 Drawable 几何体对象都可以使用 setUpdateCallback 设 置更新回调;通过具现 NodeCallback::operator()或者 Drawable::UpdateCallback::update 函数, 可以在回调对象中添加自定义的工作。
但是,更新回调与事件回调不同之处在于:事件循环是在当一个用户交互动作或系统事件产生时,每个节点(以及 Drawable 对象)的事件回调才会被调用一次;而节点(以及 Drawable 对象)的更新回调只会在每帧中被调用一次。这一区别决定了我们应当在什么时候使用事件回调, 以及在什么时候使用更新回调。
osgViewer::Viewer::updateTraversal()
那我们就开始进入osgViewer::Viewer::updateTraversal(),updateTraversal和eventTraversal一样首先都要定义目前处在的时间以及帧数,并进行记录,这样有利于进行统计分析。下面我们就要进入osgViewer::Viewer::updateTraversal()里最重要的函数osgViewer::Scene::updateSceneGraph()函数。
这个函数中我们先介绍一下它的主体功能,再去介绍这里遇到的一些新的概念。主要功能:
1、使用DatabasePager::updateSceneGraph函数更新场景的分页数据库,异步处理在分页数据库处理线程中。
2、ImagePager::updateSceneGraph函数, 更新场景的分页图像库,异步处理在分页数据库处理线程中。
3、设置图片请求的处理器。
我们先介绍一下DatabasePager和ImagePager
DatabasePager:分页数据库。在大型三维场景中采用数据分页的方式进行动态调度。这里“分页”的意思是随着视口范围的变化,场景只加载和渲染当前视口范围内数据,并将离开视口范围内的数据清除内存(可以设定不同的数据卸载策略),不再渲染。保证内存中只有有限的数据量,场景的每一帧也只有有限的数据被送到图形渲染管道,从而提高渲染性能。
ImagePager: 分页图像库。查看ImagePager 的相关内容了。这个类的工作 性质与 DatabasePager 没什么大的区别,它主要负责的是纹理图片文件的运行时加载工作。
DatabasePager和ImagePager都会用到独立的线程进行他们自己的工作。我们想要进入读懂他们代码的内容,首先我们得具备openThread的基本知识。
openThread的基本知识
面向对象的跨平台线程库 OpenThreads 原本是独立的开源工程,OSG 2.x 以后的版本将 其纳入了自己的体系结构当中,成为 OSG 基本库的一份子。 OpenThreads 库包含了以下几个主要的线程处理类: Thread 类:线程实现类。它是一个面向对象的线程实现接口,每定义一个 Thread 类, 就相当于定义了一个共享进程资源,但是可以独立调度的线程。通过重写 run()和 cancel()这 两个成员函数,即可实现线程运行时和取消时的操作;通过调用 start()和 cancel(),可以启 动或中止已经定义的进程对象。 Mutex 类:互斥体接口类。如同 pthread 等常用的线程库那样,OpenThreads 也提供了互 斥体操作的机制,它有效地避免了各个线程对同一资源的相互竞争,即,某一线程欲操作某 一共享资源时,首先使用互斥体成员的 lock()函数加锁,操作完成之后再使用 unlock 函数解锁。一个线程类中可以存在多个 Mutex 成员,用于在不同的地点或情形下为共享区域加锁; 但是一定要在适当的时候解锁,以免造成线程的共享数据无法再访问。 Condition 类:条件量接口类。它依赖于某个 Mutex 互斥体,互斥体加锁时阻塞所在的 线程,解锁或者超过时限则释放此线程,允许其继续运行。 这里涉及了几个线程操作中重要的概念:同步,阻塞以及条件变量。线程同步,简单来 说就是使同一进程的多个线程可以协调工作,例如让它们都在指定的执行点等待对方,直到 全员到期之后才开始同步运行;拥塞,即强制一个线程在某个执行点上等待,直到满足继续 运行的条件为止。例如其它的线程到达同一执行点,某个变量初始化完成等等,可以通过条 件变量来设计各种条件。 Block 类:阻塞器类。顾名思义,这个类的作用就是阻塞线程的执行,使用 block()阻塞 执行它的线程(注意,不一定是定义它的 Thread 线程,而是当前执行了 block 函数的线程, 包括系统主进程),并使用 release()释放之前被阻塞的线程。 下图所示的代码实现了一个简单的线程,并演示了 Block 类的使用方法。运行程序后 可以发现,Block::block()函数将首先阻塞主进程,被释放后再次阻塞的是 TestThread 线程, 这与它是谁的成员变量并无关系。BlockCount 类:计数阻塞器类。它与阻塞器类的使用方法基本相同:block()阻塞线程, release()释放线程;不过除此之外,BlockCount 的构造函数还可以设置一个阻塞计数值。计 数的作用是:每当阻塞器对象的 completed()函数被执行一次,计数器就减一,直至减到零 就释放被阻塞的线程。 Barrier 类:线程栅栏类。这是一个对于线程同步颇为重要的阻塞器接口,它的构造函 数与 BlockCount 类似,可以设置一个整数值,我们可以把这个值理解成栅栏的“强度”。每 个执行了 Barrier::block()函数的线程都将被阻塞;当被阻塞在栅栏处的线程达到指定的数目时,就好比栅栏无法支撑那么大的强度一样,栅栏将被冲开,所有的线程将被释放。重要的 是,这些线程是几乎同时释放的,也就保证了线程执行的同步性。 注意 BlockCount 与 Barrier 的区别,前者是由其它任意线程执行指定次数的 completed() 函数,即可释放被阻塞的线程;而后者则是必须阻塞指定个数的线程之后,所有的线程才会 同时被释放。 ScopedLock 模板:这个模板是与 Mutex 配合出现的,它的作用域之内将对共享资源进 行加锁,作用域之外则自动解锁。
原文链接 http://www.3wwang.cn/blog/article.ftl?id=22