osg學習 osg源碼分析-最長的一幀 第五日


   第五日

     當前位置 osgViewer/Viewer.cpp463,osgViewer::Viewer::realize()

     下面我們再次遍歷所有GraphicsContext設備,對於每個GraphicsContext指針gc,判斷它是否為GraphicsWindow對象,並執行GraphicsWindow::grabFocusIfPointerInWindow函數。閱讀GraphicsWindowWin32類(即GraphicsContext的具體實現者)的同名函數可以發現,這個函數不過是負責把鼠標焦點轉到當前窗口上而已。

      下一步工作的代碼如下:

       osg::Timer::instance()->setStartTick();

       setStartTick(osg::Timer::instance()->getStartTick());

       setUpThreading();

       首先調用osg::Timer::setStartTick函數,啟動OSG內部定時器並開始計時。

       隨后,Viewer::setStartTick函數的工作是找到當前視景器和所有GraphicsContext設備的事件隊列_eventQueue,並設定它們的啟動時刻為當前時間。

      下一行是調用ViewerBase::setUpThreading函數……設置線程,對於一向以多線程渲染而聞名的OSG而言,這一定是個值得深究的話題。     

      當前位置 osgViewer/ViewerBase.cpp122osgViewer::ViewerBase:: setUpThreading()

      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函數的時候重新進行解析。

     當前位置 osgViewer/Viewer.cpp486osgViewer::Viewer::realize()

     好了,如果您還沒有忘記我們來自何方的話,請回到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的實現尚有問題,目前可能會造成系統的崩潰(不要大失所望呀^_^)。    

    解讀成果

    線程模型,osgViewer::Viewer::realize。 

    懸疑列表

    類變量_cameraWithFocus的意義是什么?如何調度和實現OSG的多線程機


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM