1. 程式人生 > >"Batch,Batch,Batch":What does it really mean?

"Batch,Batch,Batch":What does it really mean?

作者:i_dovelemon

日期:2016 / 06 / 18

來源:CSDN

主題:batch, drawl call, performance


引言

        最近,有個問題為什麼在3D圖形程式設計中,總是以draw call的數量來估測效能,draw call到底做了什麼?它與GPU,CPU到底是什麼關係?帶著這個疑問,上網搜尋了下相關的文章,發現在Stackoverflow上有關於這個的討論。在詳細閱讀了他們的討論之後,發現有人給出了Nvidia的一篇文章“"Batch,Batch,Batch":What does it really mean?”。本篇文章將主要記錄一些對該篇文章的理解。正如作者提出的那樣,這篇文章很老,是否與現在的實際情況有出入,不得而知,但是整體上能夠給我一個對draw call的初步瞭解。好了,廢話不多說,開始正文吧!!!

Let's go

圖1
圖2         開篇,文章中就提出了Batch是什麼,每當我們在API中呼叫DrawPrimitive之類的函式的時候,實際上就是向GPU提交三角形(一般來說)資料,一個Batch,具有同樣的渲染狀態,同樣的紋理,同樣的Transform。
圖3         這裡,他提出了一個問題,遊戲繪製1百萬個物體,而每一個物體具有10個三角形的效能和繪製10個物體,每一個物體具有1百萬個三角形,哪個效能好,哪個速度更快?從書上或者其他途徑,我們都瞭解後面一種實際上效率高。但是高的原因是什麼,文中提出了一些常見的錯誤猜測:         (1)在GPU上進行狀態切換不夠快(錯)         (2)在GPU上組織建立三角形很佔用資源(錯)         (3)核心中傳輸資料很慢(錯)         看完這個之後,我就知道以前我的理解原來是錯誤的。除了上面的錯誤猜測之外,還提出了疑問“未來的GPU能夠解決在兩種繪製情況下都同樣高效繪製的問題嗎?”

圖4
圖5         作者說,不要進行猜測,我們實際編寫程式碼,測試看看,答案就知道了。通過編寫一個測試程式碼,僅僅繪製一些非常簡單的三角形資料,沒有光照,沒有紋理,去掉任何不必要的開銷,僅僅提交batch。圖5中,給出了測量的結果。從中,我們能夠了解一些基本引數,橫軸是triangles/batch,表示每一個batch中所容納的triangles數量。縱軸是million triangles/s,表示1s能夠繪製的百萬級三角形資料。不同的GPU,相同的CPU,在具有不同尺寸的batch時,繪製的效率明顯不同,並且不是我們想象中的線性關係,而是在某個點的時候,突變,繪製瞬間提高。

圖6         據此,我們能夠得出一些優化的方案,選擇合適batch尺寸,能夠大大的提升效能。
圖7         圖7給出了一個結論,從中可以看出,當batch的尺寸小於130的時候,GPU其實遠沒有滿載,效率低下的原因在CPU這邊,CPU沒有辦法提交更多的batch讓GPU進行處理。
圖8         從前面的實驗,以及得出的結果中可以看到,整個繪製的效率瓶頸在CPU這邊,而不是在GPU。也就是說,CPU沒有處理足夠多的資料,是繪製效率瓶頸的直接原因,而不是因為我們的batch尺寸的關係。下面,我們來統計下每一秒,能夠提交的batch數量。
圖9
圖10
圖11
圖12
圖13         從上面的統計圖中能夠得出,同一個CPU在不同的GPU,不同的batch尺寸下,每秒能夠提交的batch數量實際上是恆定的。不同的CPU每秒提交的batch數不相同。也就是說整個系統每秒能夠繪製的batch數,只和CPU有關,而和batch的尺寸,GPU的種類等等無關。
圖14         從前面的分析,我們就能夠知道,瓶頸真的完完全全在CPU這端。CPU一直在忙著向GPU提交batch。
圖15         這張圖給出了一個CPU在處理batch尺寸在2個三角形時的CPU資源分佈,78%的資源被驅動佔用了,另外的14%被D3D佔用,剩下的東西被其他部分佔用。驅動實際上在每一次的draw和狀態改變的時候,都進行很少的工作,但是如果工作的很頻繁,這個部分也會佔用非常大的資源。圖形驅動一直在被優化,但是不管怎麼優化,都需要耗費十分多的資源。對CPU來說,CPU耗費的週期與batch數的提交數量是呈現線性關係的。CPU處理batch的時間複雜度很難降低到常量級。
圖16         前面說過,整個繪製系統的瓶頸在CPU這端,而不在GPU。從上圖可以看出,GPU的本身的計算速度和發展速度都要比CPU高的多。如果你的batch尺寸很小,而CPU提交該batch在驅動上花費的時間不會減少,GPU速度又過快的處理了這些資料,就會導致GPU處在空閒狀態,整體每秒繪製的三角形當然就會減少很多。如果你的遊戲需要的三角形數目是固定的,那麼只有花費更多的時間才能夠繪製完畢,自然就導致了幀率下降。所以,為了不讓CPU在驅動上面花的時間浪費掉,我們儘量給每一個batch提供更多的三角形資料,由於GPU更快,不會導致CPU等待GPU處理完畢資料的情況(本實驗而言,如果有很複雜的shader計算,也會出現這種情況),那麼整個系統能夠繪製的三角形數量自然就會提高好多,遊戲場景就能夠更快的被繪製出來。
圖17         由於CPU提交batch所花費的時間主要在驅動和D3D的Runtime上面,所以,當我們優化了驅動和D3D Runtime,提高了CPU的計算,我們每秒能夠提交的batch數量自然就會提高好多。GPU實際上處理最終傳遞過來的三角形資料,也就是說batch的尺寸實際上影響的是GPU,當GPU速度提升了之後,我們自然就能夠使用更大尺寸的batch了。同時還要明白一點,GPU的速度遠遠的高於CPU,這也是為什麼batch的尺寸不會影響到CPU的原因所在。
圖18
圖19         所以Batch的尺寸和效能實際上沒有太大的關係。我們在編寫遊戲的時候,也不可能將所有的物體都用很大的batch組織起來,少量的小尺寸的batch,也不會影響到什麼。batch尺寸的大小應該依據遊戲最終需要繪製場景的三角形數量,每幀提交的batch數目以及GPU的速度,來合理的設定它。而每幀提交的batch數目依賴與CPU的速度,目標幀率,以及我們給提交batch所預留的CPU週期。
圖20         那麼,當我們知道了一個CPU滿載的時候每秒能夠提交的Batch數N,以及預留的CPU資源比例R,和目標FPS,F,我們就能夠通過公式         X = N * R / F         得到每一幀應該提交的batch數量。並且,我們就能夠依此設計我們的遊戲,合理安排資料,提交合適大小的batch,完成場景的繪製。
圖21
圖22
圖23         那麼Batch的尺寸該怎麼決定了,這要依賴與我們自己的決定。如果GPU還有空閒的資源,那麼我們可以通過增大batch的尺寸來提供更平滑,更精細的場景,人物模型。如果模型以及場景以及足夠,那麼我們可以將空閒的GPU資源用於提升shader,可以進行更加精細的shader計算,以得出更真實,更有吸引力的場景。
圖24         依據前面的計算公式,我們能夠得出在本實驗環境下,每一幀能夠提交的batch數量在300左右。一般來說,一個batch即是遊戲中的一個物體,那麼每一幀就只能花300個物體,還是在不考慮複雜效果的情況下,這很難讓我們做出好玩的遊戲內容出來。那麼,如果想要花很多的物體,我們就需要藉助與GPU將不同物體的batch包裝成一個batch,然後提交,以減少batch數量。
圖25         那麼,是什麼阻止了我們將一個多個物理打包成一個batch了:紋理。從前面我們知道,一個batch,它的渲染狀態要是一致的,如果不同的物理具有不同的紋理,那麼通常手段是沒有辦法把它們放在一個batch裡面進行繪製。為此,我們可以想出其他具有技巧的方法,傳遞更多的紋理,同時能夠讓不同物理的三角形能夠識別各自的紋理。比如說,對於頂點資料來說,通常位置是XYZW,而W總是為1,那麼我們是不是就可以在W中儲存一個該頂點所對應的紋理索引了,然後將所有的紋理都傳遞到紋理單元中去(注:這只是我猜想的一種方案,實際可能不會成功,因為頂點資料會進行插值,從而導致在Pixel shader中,w也可能被改變,在此僅僅是說明能夠通過某些技巧來實現)。除此之外,我們還能夠將不同物體的紋理,打包放在一個貼圖中,然後只要給這些頂點資料賦值正確的紋理座標就能夠實現一次batch提交,而描繪所有的物體。這種方法的確有效,而且經常被使用。比如人物模型的貼圖,通常都會放在一個紋理裡面,然後人物的不同部分分別對應不同的紋理座標,就能夠通過一次draw call繪製完畢整個模型。
圖26         另外一個會破壞batch無法合併的東西,就是transform矩陣。同樣的,我們也能夠通過技巧將它儲存,然後通過一次draw call繪製N個物體。
圖27         材質也會導致batch無法合併。更多關於這方面的優化手段,請自行搜尋,或者在GPU GEMS中的Graphics Pipeline Performace一章中能夠找到你想要的答案。可能有人會覺得這些優化手段不是會導致GPU變得非常慢嗎?的確,這些操作會佔用GPU的資源,但是正如我們前面所知道的那樣,GPU的速度遠遠高於CPU,大部分時候都沒有讓它滿載,我們的任務就是要讓GPU滿負荷的執行,從而實現更加快速的繪製。

總結

        Batch指得就是一次draw call呼叫。GPU的速度要遠遠的高於CPU,繪製的瓶頸往往是CPU沒有提交足夠大的batch。CPU耗費了大量的時間在驅動和3D Runtime上面,而這些耗費和Batch的尺寸並沒有直接關係,為了繪製更多的三角形,想盡所有的辦法整合batch,繪製更多的物體。         希望能夠通過本篇文章,瞭解到3D遊戲程式設計優化的基礎知識,同時也希望能夠讓更多的人喜歡上游戲開發,能夠做出更加優秀的作品!