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的實現尚有問題,目前可能會造成系統的崩潰(不要大失所望呀^_^)。