osg 线程模型
void ViewerBase::frame(double simulationTime) { if (_done) return; // OSG_NOTICE<<std::endl<<"CompositeViewer::frame()"<<std::endl<<std::endl; if (_firstFrame) { viewerInit(); if (!isRealized()) { realize(); } _firstFrame = false; } advance(simulationTime); eventTraversal(); updateTraversal(); renderingTraversals(); }
sgViewer/Viewer.cpp 第 496 行,void Viewer::realize()
void Viewer::realize() { //OSG_INFO<<"Viewer::realize()"<<std::endl; Contexts contexts; getContexts(contexts); if (contexts.empty()) { OSG_INFO<<"Viewer::realize() - No valid contexts found, setting up view across all screens."<<std::endl; // no windows are already set up so set up a default view std::string value; if (osg::getEnvVar("OSG_CONFIG_FILE", value)) { readConfiguration(value); } else { int screenNum = -1; osg::getEnvVar("OSG_SCREEN", screenNum); int x = -1, y = -1, width = -1, height = -1; osg::getEnvVar("OSG_WINDOW", x, y, width, height); if (osg::getEnvVar("OSG_BORDERLESS_WINDOW", x, y, width, height)) { osg::ref_ptr<osgViewer::SingleWindow> sw = new osgViewer::SingleWindow(x, y, width, height, screenNum); sw->setWindowDecoration(false); apply(sw.get()); } else if (width>0 && height>0) { if (screenNum>=0) setUpViewInWindow(x, y, width, height, screenNum); else setUpViewInWindow(x,y,width,height); } else if (screenNum>=0) { setUpViewOnSingleScreen(screenNum); } else { setUpViewAcrossAllScreens(); } } getContexts(contexts); } if (contexts.empty()) { OSG_NOTICE<<"Viewer::realize() - failed to set up any windows"<<std::endl; _done = true; return; } // get the display settings that will be active for this viewer osg::DisplaySettings* ds = _displaySettings.valid() ? _displaySettings.get() : osg::DisplaySettings::instance().get(); osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface(); // pass on the display settings to the WindowSystemInterface. if (wsi && wsi->getDisplaySettings()==0) wsi->setDisplaySettings(ds); unsigned int maxTexturePoolSize = ds->getMaxTexturePoolSize(); unsigned int maxBufferObjectPoolSize = ds->getMaxBufferObjectPoolSize(); for(Contexts::iterator citr = contexts.begin(); citr != contexts.end(); ++citr) { osg::GraphicsContext* gc = *citr; if (ds->getSyncSwapBuffers()) gc->setSwapCallback(new osg::SyncSwapBuffersCallback); // set the pool sizes, 0 the default will result in no GL object pools. gc->getState()->setMaxTexturePoolSize(maxTexturePoolSize); gc->getState()->setMaxBufferObjectPoolSize(maxBufferObjectPoolSize); /* 首先是 GraphicsContext::realize 函数,实际上也就是 GraphicsContext::realizeImplementation 函数。 realizeImplementation 是纯虚函数吗?没错,回想一下第三日的内容,当我们尝试使用createGraphicsContext 来创建一个图形设备上下文时,系统返回的实际上是这个函数的值: 而正如我们历经千辛万苦所分析的那样,wsref 所指向的是平台相关的 API 接口类,也就是 Win32 API 的接口,也就是 GraphicsWindowWin32.cpp 中对应类的实例。换句话说,此时 WindowingSystemInterface:: createGraphicsContext 函数返回的值,也应当是派生自GraphicsContext 的具体类的实例! 正确,对于 Windows 用户来说,这个函数返回的恰恰是 GraphicsWindowWin32 的实例,而前文的 realizeImplementation 函数,正是 GraphicsWindowWin32::realizeImplementation。 */ gc->realize(); if (_realizeOperation.valid() && gc->valid()) { gc->makeCurrent(); (*_realizeOperation)(gc); gc->releaseContext(); } } // attach contexts to _incrementalCompileOperation if attached. if (_incrementalCompileOperation) _incrementalCompileOperation->assignContexts(contexts); bool grabFocus = true; if (grabFocus) { for(Contexts::iterator citr = contexts.begin(); citr != contexts.end(); ++citr) { osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(*citr); if (gw) { gw->grabFocusIfPointerInWindow(); } } } // initialize the global timer to be relative to the current time. //首先调用 osg::Timer::setStartTick 函数,启动 OSG 内部定时器并开始计时。 osg::Timer::instance()->setStartTick(); // pass on the start tick to all the associated event queues //Viewer::setStartTick 函数的工作是找到当前视景器和所有 GraphicsContext 设备的事件队列_eventQueue,并设定它们的启动时刻为当前时间。 setStartTick(osg::Timer::instance()->getStartTick()); // configure threading. //调用 ViewerBase::setUpThreading 函数……设置线程,对于一向以多线程渲染而闻名的 OSG 而言,这一定是个值得深究的话题。 setUpThreading(); if (osg::DisplaySettings::instance()->getCompileContextsHint()) { for(unsigned int i=0; i<= osg::GraphicsContext::getMaxContextID(); ++i) { osg::GraphicsContext* gc = osg::GraphicsContext::getOrCreateCompileContext(i); if (gc) { gc->createGraphicsThread(); gc->getGraphicsThread()->startThread(); } } } #if 0 osgGA::GUIEventAdapter* eventState = getEventQueue()->getCurrentEventState(); if (getCamera()->getViewport()) { osg::Viewport* viewport = getCamera()->getViewport(); eventState->setInputRange( viewport->x(), viewport->y(), viewport->x() + viewport->width(), viewport->y() + viewport->height()); } else { eventState->setInputRange(-1.0, -1.0, 1.0, 1.0); } #endif }
OSG 的视景器包括四种线程模型,可以使用 setThreadingModel 进行设置,不同的线程模型在仿真循环运行时将表现出不同的渲染效率和线程控制特性。通常而言,这四种线程的特性如下:
SingleThreaded:单线程模型。OSG 不会创建任何新线程来完成场景的筛选和渲染,因而也不会对渲染效率的提高有任何助益。它适合任何配置下使用。
CullDrawThreadPerContext:OSG 将为每一个图形设备上下文(GraphicsContext)创建一个图形线程,以实现并行的渲染工作。如果有多个 CPU 的话,那么系统将尝试把线程分别放在不同的 CPU 上运行,不过每一帧结束前都会强制同步所有的线程。
DrawThreadPerContext:这一线程模型同样会为每个 GraphicsContext 创建线程,并分配到不同的 CPU 上。十分值得注意的是,这种模式会在当前帧的所有线程完成工作之前,开始下一帧。
CullThreadPerCameraDrawThreadPerContext:这一线程模型将为每个 GraphicsContext和每个摄像机创建线程,这种模式同样不会等待前一次的渲染结束,而是返回仿真循环并再次开始执行 frame 函数。如果您使用四核甚至更高的系统配置,那么使用这一线程模型将最大限度地发挥多 CPU 的处理能力。
与 DrawThreadPerContext 和 CullThreadPerCameraDrawThreadPerContext 这两种同样可以用于多 CPU 系统,且相对更有效率的线程模型相比,CullDrawThreadPerContext 的应用范围比较有限;而 SingleThreaded 模式在单核以及配置较低的系统上运行稳定。
这些话长篇大论地说出来似乎令人满腹疑窦:OSG 为什么要为每个 GraphicsContext 设备配置一个线程?为什么又要为每个摄像机配置一个线程?线程的同步是怎么实现的?线程与 CPU 的关系又是怎么处理的?OSG 入门书籍中常说的更新(Update)/筛选(Cull)/绘制(Draw)三线程又是在那里体现的?为什么……
天哪,这么多问题我们都要解读吗?是的,绝对要解读,不管花费多少时间!OSG学习是为了实际的应用,但是只有真正理解了它的运行机制,才能够最有效地把这个愈加著名的实时场景渲染软件用好。但是有些事情是急不来的,从 frame 函数的源代码中可以大致推测出来,场景的筛选和绘制工作是由 ViewerBase::renderingTraversals 函数来完成的。相应的,很多线程的调度和同步工作也是在这个函数中完成的,那么就让我们把问题留到那个时候吧。不过不妨先透露一点信息:第四日中我们提到的渲染器(Renderer)类,事实上也是与 OSG 的渲染线程密切相关的,因为筛选和绘制的工作就是由它来具体负责!好的,遗留的问题可以说暂时得到了解答,不过新的问题又出现了,而且任务看起来更为艰巨,继续努力好了。
线程相关的问题留待后面解决,不过还是让我们先通读一下 setUpThreading 函数的代码也无妨。它的工作主要是处理单线程(SingleThreaded)模式下,多处理器系统的数据线程分配方式。
听起来很深奥,不过实际上这里没有多么复杂。在现阶段,如果采用单线程模式的话,OSG 系统将使用 CPU0 来处理用户更新、筛选和渲染等一切事务,而使用 CPU1 来处理场景的两个分页数据库(DatabasePager)线程(它们分别用于处理本地和网络上的场景数据)。
这里还出现了一个 Viewer::getScenes 函数(osgViewer/Viewer.cpp,141 行),它的作用是获取当前视景器对应的 osgViewer::Scene 对象,也就是场景。一个场景包括了唯一的场景图形根节点,分页数据库(DatabasePager),以及分页图像库(ImagePager)。Viewer 视景器对象通常只包括一个 Scene 场景,而 CompositeViewer 复合视景器则可能包括多个场景对象。
如果系统采用了 SingleThreaded 之外的其它线程模型,那么 setUpThreading 函数将自动执行 ViewerBase::startThreading——多线程渲染的最重要函数之一,这个函数将在我们追踪到 renderingTraversals 函数的时候重新进行解析。
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(); } }
好了,如果您还没有忘记我们来自何方的话,请回到 realize 函数,现在这个函数的执行已经接近了尾声,不过我们又遇到了一个问题:编译上下文(也就是 Compile Contexts,暂时就这样翻译吧)?如果要启用它的话并不困难,只需要在调用 realize 之前执行:
osg::DisplaySettings::instance()->setCompileContextsHint(true);
随后,正如您在 realize 函数的 491-503 行之间看到的,系统将设法遍历所有可能的GraphicsContext 设备,针对它们分别再各自添加一个新的 GraphicsContext 设备(也就是说,如果系统中已经有了数个图形上下文,那么现在又将新增同样数量的图形上下文与之对应),所用的函数为 GraphicsContext::getOrCreateCompileContext。这之后,分别执行了创建图形线程,设置 CPU 依赖性,以及启动图形线程的工作,具体的实现内容可以暂时忽略。
观察 getOrCreateCompileContext 函数的内容,很快我们就可以发现其中的重点:这些新增的 GraphicsContext 对象使用了 pBuffer 的特性,并与对应的已有对象共享同一个图形上下文(Traits::sharedContext 特性)。事实上,这是 OSG 利用 OpenGL 的像素缓存(Pixel Buffer)技术,为图形上下文的后台编译提供的一种新的解决方案。这样不仅可以提高图形刷新的速度,还可以方便用户为某一特定的 GraphicsContext 设备添加特殊的处理动作,方法是使用osg::GraphicsContext::getCompileContext 获取后台图形上下文,再使用 GraphicsContext::add函数向其中追加 osg::Operation 对象,类似的例子可以参看 osgterrain。
对了,在结束这一日的旅途之前,还要提示一句:“编译上下文”这一功能在 Windows的实现尚有问题,目前可能会造成系统的崩溃(不要大失所望呀^_^)。