DrawCall的定義
cpu對(duì)圖形繪制接口的調(diào)用,CPU通過調(diào)用圖形庫(directx/opengl)接口,命令GPU進(jìn)行渲染操作。
1.Draw Call與性能
經(jīng)常,我們會(huì)提到DrawCall性能優(yōu)化。很多時(shí)候我們會(huì)誤以為DrawCall造成的性能問題是GPU切換渲染狀態(tài)導(dǎo)致,其實(shí)這里的元兇是CPU。要理解這一點(diǎn),我們就需要明白下面2個(gè)問題
CPU和GPU是如何進(jìn)行并行工作和交互的?
試想,渲染流程沒用采用流水線的工作方式:CPU發(fā)送一個(gè)渲染命令之后,GPU立即執(zhí)行渲染命令繪制圖形,等到渲染任務(wù)結(jié)束之后,CPU才可以繼續(xù)發(fā)送下一個(gè)渲染命令,這樣顯然影響工作效率。
采用渲染流水線后,CPU與GPU并行工作,獨(dú)立而不相互依賴。這是通過命令緩沖區(qū)來實(shí)現(xiàn)的:命令緩沖區(qū)維護(hù)一個(gè)命令隊(duì)列,CPU向其中發(fā)送命令,GPU從中取出命令并執(zhí)行。命令有很多種,DrawCall是一種,其他命令還有改變渲染狀態(tài)、設(shè)置渲染數(shù)據(jù)流等。
這種方式就類似于游戲開發(fā)的網(wǎng)絡(luò)通信:維持一個(gè)消息隊(duì)列,網(wǎng)絡(luò)線程接收解析消息并將之添加到消息隊(duì)列,游戲主線程更新時(shí)從中取出消息并做派發(fā)處理。
通俗的來說就是Cpu:(#`O′)喂你好,是Gpu嗎?快點(diǎn)醒醒我這里又有畫畫的任務(wù)了(Cpu調(diào)用Gpu的次數(shù)),打一個(gè)比方比如上傳很多文件到百度云或其他地方時(shí),都會(huì)把它壓縮到一個(gè)文件夾里,不會(huì)把它們分開上傳(當(dāng)然還有原因就是它們數(shù)據(jù)是相關(guān),比如是主題的一套ico文件或軟件的安裝文件),排除這些和文件整合的原因,假設(shè)網(wǎng)速?zèng)]有波動(dòng),分開傳和壓縮包,壓縮包速度一定快很多的(不僅僅是因?yàn)閴嚎s包更小),主要是每次上傳還有一些預(yù)備動(dòng)作(比如與服務(wù)器鏈接,初始化Socket等等),細(xì)心的會(huì)發(fā)現(xiàn)文件當(dāng)拖動(dòng)到百度云會(huì)有幾毫秒的延遲。其實(shí)優(yōu)化DrawCall主要是Cpu的處理速度的優(yōu)化,Cpu和Gpu是并行工作的,處理的方式有一個(gè)命令緩存區(qū),具體如圖所示:

別看圖中畫的好像是Cpu在等待Gpu,實(shí)際上Cpu才是拖后腿的那個(gè),現(xiàn)實(shí)中Gpu早就把命令緩存區(qū)里命令都處理完畢了,Cpu確還在準(zhǔn)備DrawCall的命令,Cpu通過圖像編程接口向命令緩存區(qū)添加命令,而Gpu通過緩存區(qū)獲取命令處理。
DrawCall是如何影響性能的?
每一次繪制CPU都要調(diào)用DrawCall,而在調(diào)動(dòng)DrawCall前,CPU還要進(jìn)行很多準(zhǔn)備工作:檢測渲染狀態(tài)、提交渲染所需要的數(shù)據(jù)、提交渲染所需要的狀態(tài)。
而GPU本身具有很強(qiáng)大的計(jì)算能力,可以很快就處理完渲染任務(wù)。
當(dāng)DrawCall過多,CPU就會(huì)很多額外開銷用于準(zhǔn)備工作,CPU本身負(fù)載,而這時(shí)GPU可能閑置了。
做個(gè)試驗(yàn):拷貝1000個(gè)總大小1M的文件和單個(gè)大小為1M的文件,明顯拷貝1000個(gè)文件要慢很多,DrawCall調(diào)用和這個(gè)很類似。
(在每次調(diào)用DrawCall之前,因?yàn)镃pu需要向Gpu發(fā)送很多內(nèi)容,包括數(shù)據(jù)、狀態(tài)和命令,在這個(gè)階段Cpu需要完成很多工作,比如檢查渲染狀態(tài)等(有一堆工作要Cpu處理,才會(huì)存放到緩存區(qū)),存放到緩存區(qū)以后,Gpu就要開始工作了,Gpu渲染能力還是很強(qiáng)的,渲染200或2000個(gè)三角網(wǎng)格通常看不出區(qū)別,導(dǎo)致Gpu渲染速度大于Cpu的提交速度,影響渲染流水線速度就是提交比較慢的Cpu(現(xiàn)在知道玩游戲要買的電腦配置了吧,一般選擇Cpu比較好的,Gpu一般的即可,當(dāng)然游戲畫面特別好的,還是建議把顯卡買好點(diǎn)的,有些游戲硬性條件普通顯卡根本渲染不了,并不是渲染速度的問題了),最后可想而知Cpu會(huì)花費(fèi)大量的時(shí)間在提交DrawCall的路上,造成Cpu的過載,Gpu確會(huì)出現(xiàn)空閑。)
2.DrawCall優(yōu)化:減少DrawCall
既然,我們已經(jīng)知道DrawCall導(dǎo)致的性能問題在于DrawCall數(shù)量過多,那么我們優(yōu)化的思路就是減少DrawCall。這里我們只討論批處理(Batching)。
過多的DrawCall會(huì)造成CPU的性能瓶頸:大量時(shí)間消耗在DrawCall準(zhǔn)備工作上。很顯然的一個(gè)優(yōu)化方向就是:盡量把小的DrawCall合并到一個(gè)大的DrawCall中,這就是批處理的思想。
使用批處理我們需要在CPU和RAM中合并網(wǎng)格,而合并網(wǎng)格本身是需要計(jì)算消耗,而且創(chuàng)建新網(wǎng)格也會(huì)占用內(nèi)存。因此批處理的頻次不宜太高,不然造成的消耗可能得不償失。
使用批處理的注意事項(xiàng):
合并的網(wǎng)格會(huì)在一次渲染任務(wù)中進(jìn)行繪制,他們的渲染數(shù)據(jù),渲染狀態(tài)和shader都是一樣的,因此合并的條件至少是:同材質(zhì)、同貼圖、同shader。最好網(wǎng)格頂點(diǎn)格式也一致。
盡量避免使用大量小的網(wǎng)格,當(dāng)確實(shí)需要時(shí),考慮是否要合并。
避免使用過多的材質(zhì),盡量共享材質(zhì)。
網(wǎng)格合并的頂點(diǎn)數(shù)量有上限(Unity中好像是65535)
合并本身有消耗,因此盡量在編輯器下進(jìn)行合并
確實(shí)需要在運(yùn)行時(shí)合并的,將靜態(tài)的物體和動(dòng)態(tài)的物體分開合并:靜態(tài)的合并一次就可以,動(dòng)態(tài)的只要有物體發(fā)生變換就要重新合并。