array 发表于 2008-9-5 13:30:48

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

当前位置:osgViewer/Renderer.cpp第334行,osgViewer::Renderer::draw ()
这个函数的基本执行流程如下:

1、从绘制队列_drawQueue中取出一个场景图形(SceneView)对象。

2、执行Renderer::compile,这个函数目前还没有作用。

3、执行Renderer::initialize,初始化Renderer绘制所需的基本变量。

4、下一步的工作是执行SceneView::getDynamicObjectCount函数判断场景视图中动态对象(设置为DYNAMIC)的个数,并执行其回调类(此回调类派生自线程阻塞器BlockCount,此处为State::getDynamicObjectRenderingCompletedCallback)的completed函数。

如果您还记得我们在十一日中介绍的内容的话,也许能理解这里completed函数的作用,也就是将阻塞器的引用计数减一,减到零时自动释放所阻塞的线程。不过,对于单线程模型来说,这一步骤基本上没有用处。它实质上是为了在多线程工作时保证动态(DYNAMIC)对象的更改不会影响到渲染管线而实现的:正如OSG基础教程中所强调的那样,只有设置为setDataVariance(DYNAMIC)的对象才可以在仿真循环中被随时更改。我们将在讲解DrawThreadPerContext线程的工作流程时再对此作详细介绍。

5、执行OpenGLQuerySupport::checkQuery函数,判断是否可以使用OpenGL查询对象(query objects)。

6、执行OpenGLQuerySupport::beginQuery函数,创建或者获取一个查询对象,其工作主要是获取并统计GPU计算的时间。

7、执行SceneView::draw函数,果然,场景的绘制工作最后也是在SceneView函数中完成的!看来代码解读的重心无疑要偏移到这个历史悠久的“场景视图类”当中了。

8、将已经结束绘制的场景视图对象再次追加到_availableQueue队列中,这样可以保证该队列始终保存有两个SceneView对象,以正确实现场景的筛选和渲染工作。

9、执行Renderer::flushAndCompile函数。它的作用是执行场景中所有过弃对象的删除工作,单线程模型下将直接执行osg::flushDeletedGLObjects函数;而对于多线程模型,由于每个图形设备(GraphicsContext)都分配了一个操作线程(见第五日的内容),因此将向线程追加一个新的osg::FlushDeletedGLObjectsOperation操作,它所调用的函数实质上与单线程模式下相同。FlushDeletedGLObjectsOperation继承自Operation类,有关它的调用时机的问题……也许我们还需要让它在“悬疑列表”中再等待一段时间。

需要删除的对象包括过弃的显示列表,纹理,着色器等,具体的方法可以参考releaseGLObjects函数,它在多个类当中都有实现。

flushAndCompile还负责分页数据库的预编译工作(即DatabasePager::compileGLObjects函数的内容)。在“悬疑列表”中我们搁置了对于compileGLObjects函数的介绍,现在可以兑现了。

这个函数的主要工作其实并不复杂:首先,从DatabasePager的“待编译列表”中取出一个数据,并从它的DataToCompileMap映射表中取出待编译的几何体(Drawable)队列和渲染状态(StateSet)队列。

对于Drawable对象,执行Drawable::compileGLObjects,创建几何体的显示列表(使用我们熟悉的glNewList);对于StateSet对象,执行StateSet::compileGLObjects,进而执行各种渲染属性的预编译命令(例如Texture对象要使用glTexImage2D等函数执行纹理数据的加载,而Program对象要执行GLSL代码的载入和编译)。
最后,从“待编译列表”中剔除已经编译的数据。注意每一帧的过程中没有必要编译所有的对象,使用DatabasePager::setMaximumNumOfObjectsToCompilePerFrame可以设置每一帧从“待编译列表”中取出多少数据执行编译工作,缺省值为4。

10、执行OpenGLQuerySupport::endQuery,结束查询对象的工作。

11、计算场景绘制所需的时间,并传递给记录器(ViewerBase::getStats)。

当前位置:osgViewer/Renderer.cpp第452行,osgViewer::Renderer::draw ()
好了,现在我们已经大体上明确了单线程模型(SingleThreaded)下OSG渲染遍历的工作流程。事实上无论是场景的筛选还是绘制工作,最后都要归结到场景视图(SceneView)的相应实现函数中去完成,渲染器类Renderer只是一个更为方便和直观的公用接口而已。下图中演示了单线程运行时,OSG系统的场景图形,摄像机,图形设备,渲染器和场景视图的关系:
附图1

OSG视景器的摄像机(包括主摄像机_camera和从摄像机组_slaves)均包括了与其对应的渲染器(Renderer)和图形设备(GraphicsContext);同时,当我们使用setSceneData将场景图形的根节点关联到视景器时,这个根节点实质上被添加为此Viewer对象中每个主/从摄像机的子节点(使用View::assignSceneDataToCameras函数),因而我们可以通过改变摄像机的观察矩阵来改变我们观察整个场景的视角。

场景的筛选(CULL)和绘制(DRAW)工作实质上都是由内部类osgUtil::SceneView来完成的,但是OSG也为场景渲染的工作提供了良好的公用接口,就是“渲染器”。渲染器Renderer负责将场景绘制所需的各种数据(OpenGL状态值,显示设置,筛选设置等)传递给SceneView对象,并调用SceneView::cull和SceneView::draw函数,以完成场景的筛选/绘制工作。

摄像机所对应的图形设备(GraphicsContext)同样也可能负责调用SceneView::draw函数,这与我们选择的线程模型有关。事实上,由于OSG的多线程模型将为每一个图形设备创建一个专门的工作线程(使用GraphicsContext::createGraphicsThread函数),并在其中处理与场景绘制相关的诸多工作,因此GraphicsContext类在某种意义上也可以视作SceneView的一个公有实现接口(不过它更重要的意义在于,它是OSG与特定操作系统平台API的接口,参见第四-六日的内容)。

场景视图的工作过程中将遍历场景图形的根节点,此时只要获取对应摄像机的子节点就可以了。

下面我们就正式进入SceneView的内部,看看OSG那高效的渲染后台,到底是个什么样子。

解读成果:
Renderer::draw,摄像机/渲染器/场景视图的关系。
悬疑列表:
什么是“渲染目标实现方式”?Operation对象在线程中的应用时机是什么?全局渲染状态_globalStateSet有什么作用?

shengrendan2 发表于 2008-9-27 16:41:22

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