探索未知種族之osg類生物---呼吸分解之advance

回顧
我們用了兩節的內容才堪堪講解完ViewerBase::frame()函數中調用的realize()---Viewer:: realize()函數。我們簡單的總結就是Viewer:: realize()主要是使GraphicsContext處於可用狀態,並且啓動相關的圖形線程。

探索未知種族之osg類生物---呼吸分解之advance

ViewerBase::frame()函數解讀到這裏,我們完成了osg生物第一次嘗試呼吸所需要的所有器官的初始化工作。下面就真正的開始進入osg呼吸動作的研究了。也就意味着我們真是進入osg的仿真循環的研究當中。那我們就來看看osg呼吸的第一個動作advance()。

osgViewer::advance()
osgViewer::advance()函數的功能算是比較簡單的,老規矩先介紹一下這個函數中遇到的新的成員變量。_frameStamp:(osg::FrameStamp)是用來記錄osg的幀數以及時鐘校準,計數所用到的內置器官,這樣可以精確的掌握osg的運行時間,有利於開發人員進行調優工作。在這裏(advance())

double previousReferenceTime = _frameStamp->getReferenceTime();
unsigned int previousFrameNumber = _frameStamp->getFrameNumber();
_frameStamp->setFrameNumber(_frameStamp->getFrameNumber()+1);
首先獲得osg運行的上一幀時間(是在osg內部記錄的時間不是真實世界的時間),以及獲得已經運行了多少幀了,並使記錄加1(也就是記錄目前所處的幀數),

_frameStamp->setReferenceTime( osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick()) );

if (simulationTime==USE_REFERENCE_TIME)
{
_frameStamp->setSimulationTime(_frameStamp->getReferenceTime());
}
else
{
_frameStamp->setSimulationTime(simulationTime);
}
再設置現在的相對運行的時間(根據當前時刻,重新記錄參考時間,並因此得到兩次記錄之間的差值,即一幀經歷的時間)。

// update previous frame stats
double deltaFrameTime = _frameStamp->getReferenceTime() - previousReferenceTime;
getViewerStats()->setAttribute(previousFrameNumber, "Frame duration", deltaFrameTime);
getViewerStats()->setAttribute(previousFrameNumber, "Frame rate", 1.0/deltaFrameTime);

// update current frames stats
getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Reference time", _frameStamp->getReferenceTime());
記錄這些的目的就是有時候我們需要將幀速率,參考時間等內容予以記錄並顯示給用戶,此時需要通過 ViewerBase::getStats 函數獲得 osg::Stats 對象,用以進行幀狀態的保存和顯示。

if (osg::Referenced::getDeleteHandler())
{
osg::Referenced::getDeleteHandler()->flush();
osg::Referenced::getDeleteHandler()->setFrameNumber(_frameStamp->getFrameNumber());
}
上一段內容基本對advance介紹完成了,只剩下最後一個if (osg::Referenced::getDeleteHandler())判斷。它的作用是用來將已經收集得到的所有的osg棄用的對象刪除(osg::DeleteHandler::flush())。這裏所說的“棄用”,與我們非常熟悉的 osg::ref_ptr 智能指針是密切相關的。我們已經知道,ref_ptr 採用內存引用計數的方式,當一個場景對象(通常是 Node 節點)鏈接到根節點或者其他節點時,它的引用計數加一,這一動作是通過 ref_ptr::ref()函數實現的;如果它被剔除出節點,那麼它的引用計數減一,執行這一工作的函數是 ref_ptr::unref()。unref 函數的另一個重要任務是檢查對象的引用計數值是否到達零,如果已經沒有被其它對象所引用, 那麼稱這個對象被“棄用”,它應當被立即刪除,以釋放相應的內存空間,避免泄露。

osg::DeleteHandler與osg::ref_ptr
C++中通用的刪除對象的方法是 delete,OSG 的智能指針也是採用這種方式來釋放對 象的,不過由於OSG採用多線程更新/渲染的方式, 這樣做可能帶來會某些隱患,想象這樣一種情況:

1、場景某個的節點負責顯示某種圖形,它的工作一直很正常。

2、我們採用 DrawThreadPerContext 或者 CullThreadPerCameraDrawThreadPerContext 線程模型。

3、假設我們在更新工作中立即將這個節點刪除,而上次渲染工作可能正要將這個節點 中的數據送往 OpenGL 圖形渲染管線,那麼災難就發生了……

看到這裏,你一定已經想到了一種解決方案。對,就是在渲染後臺也使用 ref_ptr 來引用(ref)圖形節點,然後在渲染結束取消引用(unref),這樣不就可以避免無謂的犧牲了嗎?也省卻用戶的很多麻煩。

說得有道理,不過這其中恐怕忽視了一個核心的問題:渲染效率。是的,假設我們要渲染成千上萬個這樣的幾何體節點(這對您來說也許簡直是家常便飯),如果每個節點的渲染 都要多執行一次 ref/unref 的話,效率的損失將是無法被忽略的。事實上經過測算,CPU 時間的流失大概可以達到 6%,對於一個實時渲染系統來說,這的確值得斟酌。

因此,OSG 的新版本中提出了 DeleteHandler 的概念,也就是“垃圾收集”,把那些引 用計數已經爲零的對象統一收集起來,確保它們不會再被渲染線程用到之後,再在適當的地 方予以釋放。DeleteHandler 有一個重要的參數_numFramesToRetainObjects,它的意義是,垃 圾對象被收集之後,再經過多少幀(默認設置是 2),方予以釋放。因此,OSG 的垃圾收集 器同樣需要使用 DeleteHandler::setFrameNumber 來記錄當前的幀數。 這個概念提出的時間並不長,也許還需要一段時間的測試,也許會有更好的方案來替代 它。目前,OSG 的發行版本仍然採用第一種方式,也就是渲染後臺採用 ref_ptr 引用計數的 方式來避免刪除對象造成的問題;如果您想要嘗試使用和幫助調試 DeleteHandler 的話,可 以在自己的程序中(main 函數之前)加入:

#undef OSGUTIL_RENDERBACKEND_USE_REF_PTR

以請求使用 DeleteHandler。

歡迎大家來我的新家看一看 www.3wwang.cn

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章