文章目录
- 一、osgViewer:: ViewerBase::setUpThreading()
- 二、osgViewer:: ViewerBase::stopThreading()
- 三、osgViewer:: ViewerBase::startThreading()
- 四、osgViewer:: ViewerBase::renderingTraversals()
- part1 首先使用ViewerBase::checkWindowStatus检查是否存在有效的图形设备,不存在的话,需要使用ViewerBase::stopThreading停止线程运行+记录渲染遍历开始/结束时的各种属性
- part2 遍历视景器对应的所有Scene场景(Viewer单视景器只存在一个场景),记录分页数据库的更新启动帧(使用DatabasePager::signalBeginFrame,这将决定DatabasePager中的数据请求是否过期),并计算场景节点的边界球。
- part3 获取当前所有的图形设备(GraphicsContext,开头已经获得)和摄像机。
- part4 遍历所有摄像机的渲染器(Renderer),执行Renderer::cull场景筛选的操作!
- part5 遍历所有的图形设备,设置渲染上下文(使用ViewerBase::makeCurrent)并执行GraphicsContext::runOperations,实现场景绘制的操作!
- part6 再次遍历所有的图形设备,执行双缓存交换操作(GraphicsContext::swapBuffers),熟悉图形编程的朋友一看便知,这是避免动态绘图时产生闪烁的重要步骤。
- part7 遍历视景器中的场景,告知分页数据库更新已经结束(DatabasePager::signalEndFrame)
- part8 释放当前的渲染上下文(ViewerBase::releaseContext)
- 可知part4和part5的操作是整个函数的,以致整个OSG系统的重点。
- 五、osgViewer::Renderer::cull()
- 六、osgViewer::Renderer::draw ()
- 七、osgUtil:: SceneView::cull ()
一、osgViewer:: ViewerBase::setUpThreading()
在osgViewer::Viewer::realize()中使用了该接口。
如果用户没有设置线程模型,则使用ViewerBase::suggestBestThreadingModel自动进行判断:
- 如果定义了环境变量OSG_THREADING,且其中内容为四个字符串中的一个,则采用对应的模型,“SingleThreaded”(适用于配置低),“CullDrawThreadPerContext”(不建议使用),“DrawThreadPerContext”,“CullThreadPerCameraDrawThreadPerContext”。模型参考
- 如果当前不存在图形设备或者摄像机,则采用SingleThreaded模型。
- 如果当前只存在一个图形窗口(这是最常见的情况),则采用SingleThreaded模型(单核)或DrawThreadPerContext模型(多核)。
- 如果系统CPU数超过当前图形设备和摄像机数总合,为了充分发挥每个CPU的能力,将采用CullThreadPerCameraDrawThreadPerContext模型。
- 以上情形均不符合时,采用DrawThreadPerContext模型。
void ViewerBase::setUpThreading()
{
if (_threadingModel==AutomaticSelection)
{
_threadingModel = suggestBestThreadingModel();
}
// if required configure affinity before we start threads
if (_useConfigureAffinity) configureAffinity();
Contexts contexts;
getContexts(contexts);
// set up affinity of main thread
OpenThreads::SetProcessorAffinityOfCurrentThread(_affinity);
// set up the number of graphics contexts.
{
Scenes scenes;
getScenes(scenes);
for(Scenes::iterator scitr = scenes.begin();
scitr != scenes.end();
++scitr)
{
if ((*scitr)->getSceneData())
{
// update the scene graph so that it has enough GL object buffer memory for the graphics contexts that will be using it.
(*scitr)->getSceneData()->resizeGLObjectBuffers(osg::DisplaySettings::instance()->getMaxNumberOfGraphicsContexts());
}
}
}
if (_threadingModel==SingleThreaded)
{
if (_threadsRunning) stopThreading();
}
else
{
if (!_threadsRunning) startThreading();
}
}
二、osgViewer:: ViewerBase::stopThreading()
- 使用Viewer::getContexts函数获取当前所有的图形设备(GraphicsContext);
- 使用Viewer::getCameras函数获取当前所有的摄像机(主摄像机和所有从摄像机)。
- 并重置当前的图形线程和相机线程。
- 重置对于单线程模型来说无用的值:
1._threadsRunning
2.ViewerBase::_startRenderingBarrier:可以理解为渲染启动的一个栅栏标志,用于同步开始所有的图形设备的线程操作。
3.ViewerBase::_endRenderingDispatchBarrier:渲染结束的一个栅栏标志,用于同步结束所有的图形设备的线程操作。
4.ViewerBase::_endDynamicDrawBlock:用于同步结束所有的动态对象绘制操作,这里所谓的动态对象,指得是使用Object::setDataVariance设置为DYNAMIC的场景对象。
void ViewerBase::stopThreading()
{
if (!_threadsRunning) return;
OSG_INFO<<"ViewerBase::stopThreading() - stopping threading"<<std::endl;
Contexts contexts;
getContexts(contexts);
Cameras cameras;
getCameras(cameras);
Contexts::iterator gcitr;
Cameras::iterator citr;
for(Cameras::iterator camItr = cameras.begin();
camItr != cameras.end();
++camItr)
{
osg::Camera* camera = *camItr;
Renderer* renderer = dynamic_cast<Renderer*>(camera->getRenderer());
if (renderer) renderer->release();
}
// delete all the graphics threads.
for(gcitr = contexts.begin();
gcitr != contexts.end();
++gcitr)
{
(*gcitr)->setGraphicsThread(0);
}
// delete all the camera threads.
for(citr = cameras.begin();
citr != cameras.end();
++citr)
{
(*citr)->setCameraThread(0);
}
for(Cameras::iterator camItr = cameras.begin();
camItr != cameras.end();
++camItr)
{
osg::Camera* camera = *camItr;
Renderer* renderer = dynamic_cast<Renderer*>(camera->getRenderer());
if (renderer)
{
renderer->setGraphicsThreadDoesCull( true );
renderer->setDone(false);
}
}
_threadsRunning = false;
_startRenderingBarrier = 0;
_endRenderingDispatchBarrier = 0;
_endDynamicDrawBlock = 0;
OSG_INFO<<"Viewer::stopThreading() - stopped threading."<<std::endl;
}
三、osgViewer:: ViewerBase::startThreading()
四、osgViewer:: ViewerBase::renderingTraversals()
OSG的场景渲染过程可以简单地分为三个阶段:
- 用户(APP)阶段,更新用户数据,负责场景对象的运动和管理等等.
- 筛选(CULL)阶段,负责对场景中的对象进行筛选裁减,略过那些不会被用户所见(因而不必渲染)的物体,并根据渲染状态的相似性对即将进入渲染管线的对象排序(从而避免OpenGL状态量的频繁切换.
- 绘制(DRAW)阶段,执行各种OpenGL操作,将数据送入OpenGL渲染管线及显示系统处理.v
如果有多个图形设备(渲染窗口)时,需要分别为每个窗口的每个摄像机执行相应的筛选和绘制工作,因为各个摄像机的投影矩阵和观察矩阵均可能不同;不过用户(APP)阶段不需要被执行多次,因为用户数据应当是被各个图形设备所共享的。
- 根据线程模式不同,用户/筛选/绘制可以将前后两帧的工作稍微有所交叠;
- 用户更新(APP)和场景筛选(CULL),以及场景筛选和绘制(DRAW)的工作互相不能重叠;
- 但是我们可以允许在上一帧的绘制没有结束之前,就开始下一帧的用户数据更新工作;我们还可以允许由不同的CPU来执行不同图形设备的筛选和绘制工作,从而提高整体渲染的效率,实现实时渲染的目的。
参考
part1 首先使用ViewerBase::checkWindowStatus检查是否存在有效的图形设备,不存在的话,需要使用ViewerBase::stopThreading停止线程运行+记录渲染遍历开始/结束时的各种属性
void ViewerBase::renderingTraversals()
{
Contexts contexts;
getContexts(contexts);
// check to see if windows are still valid
checkWindowStatus(contexts);
if (_done) return;
double beginRenderingTraversals = elapsedTime();
osg::FrameStamp* frameStamp = getViewerFrameStamp();
unsigned int frameNumber = frameStamp ? frameStamp->getFrameNumber() : 0;
if (getViewerStats() && getViewerStats()->collectStats("scene"))
{
Views views;
getViews(views);
for(Views::iterator vitr = views.begin();
vitr != views.end();
++vitr)
{
View* view = *vitr;
osg::Stats* stats = view->getStats();
osg::Node* sceneRoot = view->getSceneData();
if (sceneRoot && stats)
{
osgUtil::StatsVisitor statsVisitor;
sceneRoot->accept(statsVisitor);
statsVisitor.totalUpStats();
unsigned int unique_primitives = 0;
osgUtil::Statistics::PrimitiveCountMap::iterator pcmitr;
for(pcmitr = statsVisitor._uniqueStats.GetPrimitivesBegin();
pcmitr != statsVisitor._uniqueStats.GetPrimitivesEnd();
++pcmitr)
{
unique_primitives += pcmitr->second;
}
stats->setAttribute(frameNumber, "Number of unique StateSet", static_cast<double>(statsVisitor._statesetSet.size()));
stats->setAttribute(frameNumber, "Number of unique Group", static_cast<double>(statsVisitor._groupSet.size()));
stats->setAttribute(frameNumber, "Number of unique Transform", static_cast<double>(statsVisitor._transformSet.size()));
stats->setAttribute(frameNumber, "Number of unique LOD", static_cast<double>(statsVisitor._lodSet.size()));
stats->setAttribute(frameNumber, "Number of unique Switch", static_cast<double>(statsVisitor._switchSet.size()));
stats->setAttribute(frameNumber, "Number of unique Geode", static_cast<double>(statsVisitor._geodeSet.size()));
stats->setAttribute(frameNumber, "Number of unique Drawable", static_cast<double>(statsVisitor._drawableSet.size()));
stats->setAttribute(frameNumber, "Number of unique Geometry", static_cast<double>(statsVisitor._geometrySet.size()));
stats->setAttribute(frameNumber, "Number of unique Vertices", static_cast<double>(statsVisitor._uniqueStats._vertexCount));
stats->setAttribute(frameNumber, "Number of unique Primitives", static_cast<double>(unique_primitives));
unsigned int instanced_primitives = 0;
for(pcmitr = statsVisitor._instancedStats.GetPrimitivesBegin();
pcmitr != statsVisitor._instancedStats.GetPrimitivesEnd();
++pcmitr)
{
instanced_primitives += pcmitr->second;
}
stats->setAttribute(frameNumber, "Number of instanced Stateset", static_cast<double>(statsVisitor._numInstancedStateSet));
stats->setAttribute(frameNumber, "Number of instanced Group", static_cast<double>(statsVisitor._numInstancedGroup));
stats->setAttribute(frameNumber, "Number of instanced Transform", static_cast<double>(statsVisitor._numInstancedTransform));
stats->setAttribute(frameNumber, "Number of instanced LOD", static_cast<double>(statsVisitor._numInstancedLOD));
stats->setAttribute(frameNumber, "Number of instanced Switch", static_cast<double>(statsVisitor._numInstancedSwitch));
stats->setAttribute(frameNumber, "Number of instanced Geode", static_cast<double>(statsVisitor._numInstancedGeode));
stats->setAttribute(frameNumber, "Number of instanced Drawable", static_cast<double>(statsVisitor._numInstancedDrawable));
stats->setAttribute(frameNumber, "Number of instanced Geometry", static_cast<double>(statsVisitor._numInstancedGeometry));
stats->setAttribute(frameNumber, "Number of instanced Vertices", static_cast<double>(statsVisitor._instancedStats._vertexCount));
stats->setAttribute(frameNumber, "Number of instanced Primitives", static_cast<double>(instanced_primitives));
}
}
}
。。。。。。
if (getViewerStats() && getViewerStats()->collectStats("update"))
{
double endRenderingTraversals = elapsedTime();
// update current frames stats
getViewerStats()->setAttribute(frameNumber, "Rendering traversals begin time ", beginRenderingTraversals);
getViewerStats()->setAttribute(frameNumber, "Rendering traversals end time ", endRenderingTraversals);
getViewerStats()->setAttribute(frameNumber, "Rendering traversals time taken", endRenderingTraversals-beginRenderingTraversals);
}
_requestRedraw = false;
}
part2 遍历视景器对应的所有Scene场景(Viewer单视景器只存在一个场景),记录分页数据库的更新启动帧(使用DatabasePager::signalBeginFrame,这将决定DatabasePager中的数据请求是否过期),并计算场景节点的边界球。
void ViewerBase::renderingTraversals()
{
。。。
Scenes scenes;
getScenes(scenes);
for(Scenes::iterator sitr = scenes.begin();
sitr != scenes.end();
++sitr)
{
Scene* scene = *sitr;
if (!scene) continue;
osgDB::DatabasePager* dp = scene->getDatabasePager();
if (dp) dp->signalBeginFrame(frameStamp);
osgDB::ImagePager* ip = scene->getImagePager();
if (ip) ip->signalBeginFrame(frameStamp);
if (scene->getSceneData())
{
// fire off a build of the bounding volumes while we
// are still running single threaded.
scene->getSceneData()->getBound();
}
}
。。。
}
part3 获取当前所有的图形设备(GraphicsContext,开头已经获得)和摄像机。
void ViewerBase::renderingTraversals()
{
。。。
Cameras cameras;
getCameras(cameras);
Contexts::iterator itr;
bool doneMakeCurrentInThisThread = false;
if (_endDynamicDrawBlock.valid())
{
_endDynamicDrawBlock->reset();
}
// dispatch the rendering threads
if (_startRenderingBarrier.valid()) _startRenderingBarrier->block();
。。。
}
part4 遍历所有摄像机的渲染器(Renderer),执行Renderer::cull场景筛选的操作!
void ViewerBase::renderingTraversals()
{
。。。
// reset any double buffer graphics objects
for(Cameras::iterator camItr = cameras.begin();
camItr != cameras.end();
++camItr)
{
osg::Camera* camera = *camItr;
Renderer* renderer = dynamic_cast<Renderer*>(camera->getRenderer());
if (renderer)
{
//条件一判断是否使用图形线程来执行场景的筛选;条件二是判断是否设置了相机线程来cull,更多详见part5
if (!renderer->getGraphicsThreadDoesCull() && !(camera->getCameraThread()))
{
//只有DrawThreadPerContext将执行renderer->cull()
//SingleThreaded和CullDrawThreadPerContext的getGraphicsThreadDoesCull为true,在runOperations将调用cull_draw
//CullThreadPerCameraDrawThreadPerContext则用相机线程来cull
renderer->cull();
}
}
}
。。。
}
part5 遍历所有的图形设备,设置渲染上下文(使用ViewerBase::makeCurrent)并执行GraphicsContext::runOperations,实现场景绘制的操作!
void ViewerBase::renderingTraversals()
{
。。。
for(itr = contexts.begin();
itr != contexts.end() && !_done;
++itr)
{
if (!((*itr)->getGraphicsThread()) && (*itr)->valid())
{
doneMakeCurrentInThisThread = true;
makeCurrent(*itr);
(*itr)->runOperations();
}
}
。。。
}
part5.1 osg::GraphicsContext::runOperations()
执行过程如下:
- 获取场景中所有注册的摄像机(包括主摄像机和从摄像机组),对它们执行排序,排序的原则根据摄像机的渲染顺序而定,可以通过Camera::setRenderOrder进行设置。设置为PRE_RENDER级别的摄像机排序在最前,而POST_RENDER级别的摄像机排序在最后;同一级别的摄像机根据setRenderOrder函数中传入的整数设置先后顺序,排序数较小的摄像机在前。
对主相机设置意义不大,因为都是以主相机为标准来排序。
- 依次遍历排序过的各个摄像机,执行其渲染器Renderer的operator()操作,它有一个传入参数,即当前的GraphicsContext图形设备。这个重载的操作符实质上执行了场景在该图形设备中的绘制工作,因此前面的排序工作将决定哪个摄像机的内容先被绘制出来。
Renderer类成员函数operator()的工作仅仅是判断是否使用图形线程来执行场景的筛选(根据Renderer::_graphicsThreadDoesCull变量的值)。
结合part4可知,只有CullDrawThreadPerContext执行cull_draw,剩下三种模式都执行draw。
- 遍历GraphicsContext::_operations队列中的各个Operation对象,执行其operator()操作。
我们可以重写自己的Operation派生对象,并通过GraphicsContext::add将其添加到图形设备的执行队列(图形设备线程)中,从而实现自己定义的OpenGL绘图功能(由于在执行runOperations函数之前已经执行了图形设备的makeCurrent函数,因此这里不必考虑渲染上下文的设置问题)。
void GraphicsContext::runOperations()
{
// sort the cameras into order
typedef std::vector<Camera*> CameraVector;
CameraVector camerasCopy;
std::copy(_cameras.begin(), _cameras.end(), std::back_inserter(camerasCopy));
std::sort(camerasCopy.begin(), camerasCopy.end(), CameraRenderOrderSortOp());
for(CameraVector::iterator itr = camerasCopy.begin();
itr != camerasCopy.end();
++itr)
{
osg::Camera* camera = *itr;
if (camera->getRenderer()) (*(camera->getRenderer()))(this);
}
for(GraphicsOperationQueue::iterator itr = _operations.begin();
itr != _operations.end();
)
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_operationsMutex);
_currentOperation = *itr;
if (!_currentOperation->getKeep())
{
itr = _operations.erase(itr);
if (_operations.empty())
{
_operationsBlock->set(false);
}
}
else
{
++itr;
}
}
if (_currentOperation.valid())
{
// OSG_INFO<<"Doing op "<<_currentOperation->getName()<<" "<<this<<std::endl;
// call the graphics operation.
(*_currentOperation)(this);
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_operationsMutex);
_currentOperation = 0;
}
}
}
}
part6 再次遍历所有的图形设备,执行双缓存交换操作(GraphicsContext::swapBuffers),熟悉图形编程的朋友一看便知,这是避免动态绘图时产生闪烁的重要步骤。
void ViewerBase::renderingTraversals()
{
。。。
// OSG_NOTICE<<"Joing _endRenderingDispatchBarrier block "<<_endRenderingDispatchBarrier.get()<<std::endl;
// wait till the rendering dispatch is done.
if (_endRenderingDispatchBarrier.valid()) _endRenderingDispatchBarrier->block();
for(itr = contexts.begin();
itr != contexts.end() && !_done;
++itr)
{
if (!((*itr)->getGraphicsThread()) && (*itr)->valid())
{
doneMakeCurrentInThisThread = true;
makeCurrent(*itr);
(*itr)->swapBuffers();
}
}
。。。
}
闪烁原因:
part7 遍历视景器中的场景,告知分页数据库更新已经结束(DatabasePager::signalEndFrame)
void ViewerBase::renderingTraversals()
{
。。。
for(Scenes::iterator sitr = scenes.begin();
sitr != scenes.end();
++sitr)
{
Scene* scene = *sitr;
if (!scene) continue;
osgDB::DatabasePager* dp = scene->getDatabasePager();
if (dp) dp->signalEndFrame();
osgDB::ImagePager* ip = scene->getImagePager();
if (ip) ip->signalEndFrame();
}
。。。
}
part8 释放当前的渲染上下文(ViewerBase::releaseContext)
void ViewerBase::renderingTraversals()
{
。。。
if (_releaseContextAtEndOfFrameHint && doneMakeCurrentInThisThread)
{
//OSG_NOTICE<<"Doing release context"<<std::endl;
releaseContext();
}
。。。
}
可知part4和part5的操作是整个函数的,以致整个OSG系统的重点。
五、osgViewer::Renderer::cull()
osgViewer::Renderer为摄像机渲染场景的工作提供了一个公有接口。当我们向视景器(Viewer)添加一个新的摄像机(Camera)时,一个与摄像机相关联的渲染器(Renderer)也会被自动创建,用于为相机节点与渲染台之间提供一个公有接口。而当我们准备渲染场景时,与特定图形设备(GraphicsContext)相关联的摄像机也会自动调用其渲染器的相应函数,执行场景筛选与绘制等工作。
- 实际上渲染器的工作是创建并初始化场景视图SceneView,每帧更新场景视图SceneView的数据((负责传递场景与用户数据)),以及执行裁减和绘制(SceneView才是真正执行者)。
osgUtil::SceneView场景视图,osgViewer::Renderer负责调用场景视图(SceneView)各种功能的,就是新增加的这个渲染器(Renderer)了,是裁减和绘制的真正执行者。
- 每个渲染器当中都会自动创建两个SceneView对象(Renderer::_sceneView[2]),从而实现了渲染后台双缓存的支持,不过SingleThreaded和CullDrawThreadPerContext环境下只使用到第一个场景视图(SceneView)。
从_availableQueue获取一个可用的场景视图,这个队列通常会保存有两个场景视图,以实现我们刚刚提到的渲染后台双缓存支持。
- 注意这里的双缓存并不是显示双缓存,而是由于多线程渲染时可能存在上一帧的DRAW工作与下一帧的CULL冲突的问题,因此使用两个初始内容一致的场景视图(SceneView),每次需要开始CULL/DRAW时,取出一个sceneView对象并用来处理场景;下一帧则取出另一个,以保证当前正在绘制过程中的sceneView不会被重写。
void Renderer::cull()
{
DEBUG_MESSAGE<<"cull()"<<std::endl;
if (_done || _graphicsThreadDoesCull) return;
// note we assume lock has already been acquired.
//从_availableQueue获取一个可用的场景视图
osgUtil::SceneView* sceneView = _availableQueue.takeFront();
DEBUG_MESSAGE<<"cull() got SceneView "<<sceneView<<std::endl;
if (sceneView)
{
// 更新这个场景视图的全局渲染状态
//(根据场景主摄像机的StateSet渲染状态集,更新成员变量(SceneView::_globalStateSet,后文介绍状态树详解),状态量(osg::State),
//显示设置(osg:: DisplaySettings)
updateSceneView(sceneView);
// OSG_NOTICE<<"Culling buffer "<<_currentCull<<std::endl;
osg::Stats* stats = sceneView->getCamera()->getStats();
const osg::FrameStamp* fs = sceneView->getFrameStamp();
unsigned int frameNumber = fs ? fs->getFrameNumber() : 0;
// do cull traversal
osg::Timer_t beforeCullTick = osg::Timer::instance()->tick();
//更新场景视图(SceneView)的融合距离(Fusion Distance)和筛选设置(CullSettings)。
//所谓融合距离,指得是双眼所在平面到视线汇聚点的距离,可以通过View::setFusionDistance函数传递给SceneView,
//通常应用于立体显示的场合。
sceneView->inheritCullSettings(*(sceneView->getCamera()));
//真正的场景筛选(裁减)工作的所在
sceneView->cull();
//记录场景筛选所耗费的时间,并保存到统计器(osg::Stats)中
osg::Timer_t afterCullTick = osg::Timer::instance()->tick();
#if 0
osg::State* state = sceneView->getState();
if (sceneView->getDynamicObjectCount()==0 && state->getDynamicObjectRenderingCompletedCallback())
{
// OSG_NOTICE<<"Completed in cull"<<std::endl;
state->getDynamicObjectRenderingCompletedCallback()->completed(state);
}
#endif
if (stats && stats->collectStats("rendering"))
{
DEBUG_MESSAGE<<"Collecting rendering stats"<<std::endl;
stats->setAttribute(frameNumber, "Cull traversal begin time", osg::Timer::instance()->delta_s(_startTick, beforeCullTick));
stats->setAttribute(frameNumber, "Cull traversal end time", osg::Timer::instance()->delta_s(_startTick, afterCullTick));
stats->setAttribute(frameNumber, "Cull traversal time taken", osg::Timer::instance()->delta_s(beforeCullTick, afterCullTick));
}
if (stats && stats->collectStats("scene"))
{
collectSceneViewStats(frameNumber, sceneView, stats);
}
_drawQueue.add(sceneView);
}
DEBUG_MESSAGE<<"end cull() "<<this<<std::endl;
}
六、osgViewer::Renderer::draw ()
http://bbs.osgchina.org/forum.php?mod=viewthread&tid=580&highlight=OSG%D4%AD%B4%B4%BD%CC%B3%CC%A3%BA%D7%EE%B3%A4%B5%C4%D2%BB%D6%A1