(轉)KlayGE游戲引擎 :高效的GBUFFER管理方式


轉載請注明出處為KlayGE游戲引擎,本文的永久鏈接為http://www.klayge.org/?p=3304

在游戲引擎里,每一幀都可能有UI和文字的渲染。這些東西的特點是,瑣碎,隨機,但每一部分的數據量很小。比如UI由很多矩形塊組成,每個只有4個頂點。這樣的數據對GPU來說是很頭疼的。所以引擎往往需要在Buffer上做一些工作來改善渲染的性能。

由於在目前常見的架構上,CPUGPU不能同時讀寫一塊內存,CPU在寫入數據的時候GPU只能讀取另一個地方來渲染。所以一定需要某個機制,來避免這樣的沖突。

常見方法1Discard

最古老的一個做法就是,自己維護一塊內存,每一次需要畫東西的時候先放在那塊內存中。每一幀用一次discard的方式對GPU buffer做一次map,把數據拷貝進去。這么做很簡單,所有復雜的同步都交給驅動去完成。

在內部,discard避免CPU/GPU沖突的方式是申請一塊新空間讓CPU填充,而GPU同時渲染的是舊空間,用完之后拋棄。也就是常說的N buffering。這么做在驅動層面至少需要2倍的內存空間,加上自己維護的那塊內存,就需要3倍空間。同時,由於空間的申請和釋放並不快,每一幀都這么做對性能來說有一定影響。

常見方法2No overwrite + Discard

自從圖形API支持no overwrite了之后,就出現了一個新方法。不需要自己維護一塊內存,而是在每次畫東西的時候直接用no overwrite的方式map GPU buffer,把數據放進去。同時,記錄下buffer是用了多少。如果已經滿了,就用discardmap,拋棄舊有空間。

No overwrite避免CPU/GPU沖突的方式是由程序自己保證,CPU新的填充數據不覆蓋到GPU需要用的數據,所以CPUGPU總是在讀寫同一塊內存的不同區域。這么做並不需要申請新空間,map的性能遠高於discard。但是這么做仍會有不少時候會遇到空間滿了需要discard。其實很多數據已經被GPU用完,完全可以復用而不必總discard

更高效的方法:transient

重新考慮一下這個情景,可以發現如果是個CPU程序,需要反復申請和釋放很多小塊的內存,那么大家都會想到用memory pool。那么為何不把memory pool的思路用到這里來呢?GDC 2012Don't Throw it all Away: Efficient Buffer Management中的Transient buffer正是如此。不久以前KlayGE的團隊成員林勝華實現了ppt中所說的transient buffer,目前UI和文字都已經轉成了用這種buffer管理方式。

Transient和思路和memory pool一樣,都是基於free list管理一塊空間。區別在於,CPUmemory poolfree list就保存在那塊空間里,每個item包含了下一個未分配區域的指針。對於GPU buffer來說,平常需要讓GPU用來渲染,所以不能處於map狀態。所以free list是在CPU端單獨維護,而每個item包含的是未分配區域在GPU buffer中的偏移量。通過這樣的間接轉換,就能在CPU端管理GPU buffer中的空閑非空閑區域了。接口方面,transient buffermemory pool也非常相似,主要都是AllocDealloc。在Alloc的時候,需要通過no overwritemap內部的GPU buffer,把數據拷貝進去。因為有free list標記所有空閑區域,所以這時候是可以非常安全地使用no overwrite,而不用擔心數據覆蓋問題。

使用transient buffer的時候,需要保存保存當前幀由Alloc返回的數據列表,渲染的時候需要一次遍歷,逐個render。每一幀結束的時候,就可以調用Dealloc把已經渲染過的塊釋放掉。但因為GPU異步的特殊性,在釋放數據上需要特別小心。每一幀CPU提交的draw,最多可能在3幀之后才真正被GPU使用。所以在transient buffer內部還需要維護一個列表,保存每一幀被Dealloc的數據,在3幀之后才真正把它標記成空閑區域。

那么,還剩一個問題是,滿了怎么辦。一個做法是discard,再把整個區域當作空閑區域。另一個做法是申請一個更大的GPU buffer,把舊的數據拷貝過去,並加長free list。第一種做法如果遇到一次Alloc就需要大於整個buffer空間的情況,還是需要退化到第二種做法。所以目前KlayGE里只實現了第二種。

不支持no overwrite的平台

對於OpenGL 3.0之前而且不支持GL_ARB_map_buffer_range,或者OpenGL ES 3.0之前而且不支持GL_EXT_map_buffer_range的平台,是沒有no overwrite的能力的。對於這樣的平台,原文沒涉及。在KlayGE中,遇到這種情況會在CPU端維護一個和GPU buffer一樣大的vector,並在渲染之前通過discard的方式拷貝給真正的GPU buffer。這保證了對上層程序來說,不必考慮no overwrite的支持度。這也是KlayGE的實現對原文的一個擴展。

未來

在目前的D3D11上,也就只能做到這個程度了。如果未來需要進一步加速,在OpenGL上可以考慮用GL_ARB_buffer_storage提供的GL_MAP_PERSISTENT_BIT,做到CPUGPU同時使用一塊Buffer的目的。D3D12改善了這點,也能同時使用,解除了目前的讀寫沖突。

另一方面,對於particle system那樣非常規則地申請和釋放內存的情況,原文中還提供了一個叫做Discard-Free Temporary Buffers的方法,來自於StarCraft 2。比transient buffer更適合這種使用模式。在以后我們也會做一些嘗試。

   



來自 <http://www.klayge.org/2015/05/25/%E9%AB%98%E6%95%88gpu-buffer%E7%AE%A1%E7%90%86%E4%B9%8Btransient-buffer/>


免責聲明!

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



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