本文轉(zhuǎn)自:字符無限科技
玩游戲時(shí)遇到畫面掉幀、操作延遲,大概率和一個(gè)叫Draw Call的指標(biāo)有關(guān)。它是游戲渲染的核心環(huán)節(jié),也是性能優(yōu)化繞不開的坎,哪怕是Unity、UE 引擎的資深開發(fā)者,也得在它身上下功夫。
什么是Draw Call?
Draw Call僅僅是一條指令!Draw Call指令從CPU傳到GPU,渲染一個(gè)網(wǎng)格。指令只指向一個(gè)被渲染的網(wǎng)格并且不包含任何材質(zhì)信息。
在發(fā)出指令后,GPU的渲染狀態(tài)值(材質(zhì)、紋理、shader等)和所有的頂點(diǎn)數(shù)據(jù)通過神奇的代碼轉(zhuǎn)化為以一個(gè)信息,然后再在你的屏幕上呈現(xiàn)出美麗的畫面。

渲染就是在做一個(gè)巨大數(shù)量的小任務(wù),比如計(jì)算成千上萬的頂點(diǎn)和在屏幕上繪制以百萬計(jì)的像素。
Draw Call 本身的含義很簡單,就是CPU調(diào)用圖像編程接口,如OpenGL 中的glDrawElements 命令或者DirectX 中的DrawlndexedPrimitive命令,以命令GPU 進(jìn)行渲染的操作。
其核心流程包含三個(gè)階段:
數(shù)據(jù)準(zhǔn)備:CPU將網(wǎng)格數(shù)據(jù)、紋理、材質(zhì)屬性等資源從內(nèi)存(RAM)傳輸至GPU顯存(VRAM);
狀態(tài)配置:設(shè)置渲染管線狀態(tài)(如著色器、混合模式、深度測試)和全局參數(shù)(如光照、投影矩陣);
指令提交:調(diào)用glDrawElements或DrawIndexedPrimitive等API觸發(fā)GPU渲染。
★關(guān)鍵特性:
命令緩沖區(qū)機(jī)制:CPU與GPU通過Command Buffer實(shí)現(xiàn)異步通信,CPU寫入指令,GPU按隊(duì)列順序執(zhí)行;
渲染狀態(tài)切換成本:每次材質(zhì)、紋理或著色器變更需重新配置全局狀態(tài),產(chǎn)生額外開銷。
你在游戲里看到的每棵樹、每個(gè)角色、每道特效,背后都需要 CPU 發(fā)一次(或多次)命令,告訴 GPU “該畫這個(gè)東西了”。比如屏幕上有 100 棵樹,默認(rèn)情況下可能就有 100 個(gè) Draw Call,GPU 收到命令后才會(huì)執(zhí)行渲染操作。
這里要明確一個(gè)關(guān)鍵點(diǎn):Draw Call就是一個(gè)命令,它的發(fā)起方是CPU,接收方是GPU。這個(gè)命令僅僅會(huì)指向一個(gè)需要被渲染的圖元(primitives)列表,而不會(huì)再包含任何材質(zhì)信息,這是因?yàn)槲覀円呀?jīng)在上一個(gè)階段中完成了!

一個(gè)常見的誤區(qū)是, Draw Call 中造成性能問題的元兇是GPU,認(rèn)為GPU 上的狀態(tài)切換是耗時(shí)的,其實(shí)不是的,真正“拖后腿”其實(shí)的是CPU。
為什么Draw Call 多了會(huì)影響幀率?
我們先來做一個(gè)實(shí)驗(yàn):先創(chuàng)建10000個(gè)小文件,每個(gè)文件的大小為1KB,然后把它們從一個(gè)文件夾復(fù)制到另一個(gè)文件夾。你會(huì)發(fā)現(xiàn),盡管這些文件的空間總和不超過10MB ,但要花費(fèi)很長時(shí)間。
現(xiàn)在,我們再來創(chuàng)建一個(gè)單獨(dú)的文件,它的大小是10MB,然后也把它從一個(gè)文件夾復(fù)制到另一個(gè)文件夾。而這次復(fù)制的時(shí)間卻少很多!
這是為什么呢?明明它們所包含的內(nèi)容大小是一樣的。原因在于,每一個(gè)復(fù)制動(dòng)作需要很多額外的操作,例如分配內(nèi)存、創(chuàng)建各種元數(shù)據(jù)等。
如你所見,這些操作將造成很多額外的性能開銷,如果我們復(fù)制了很多小文件,那么這個(gè)開銷將會(huì)很大。
渲染的過程雖然和上面的實(shí)驗(yàn)有很大不同,但從感性角度上是很類似的。在每次調(diào)用Draw Call 之前, CPU 需要向GPU 發(fā)送很多內(nèi)容,包括數(shù)據(jù)、狀態(tài)和命令等。

在這一階段, CPU 需要完成很多工作,例如檢查渲染狀態(tài)等。而一旦CPU 完成了這些準(zhǔn)備工作, GPU 就可以開始本次的渲染。
GPU 的渲染能力是很強(qiáng)的,渲染200 個(gè)還是2000 個(gè)三角網(wǎng)格通常沒有什么區(qū)別,因此渲染速度往往快于CPU 提交命令的速度。
如果Draw Call 的數(shù)量太多, CPU 就會(huì)把大量時(shí)間花費(fèi)在提交Draw Call 上,造成CPU 的過載。

如何減少Draw Call?
盡管減少Draw Call 的方法有很多,但我們這里僅討論使用批處理(Batching )的方法。
我們講過,提交大量很小的Draw Call 會(huì)造成CPU 的性能瓶頸,即CPU 把時(shí)間都花費(fèi)在準(zhǔn)備Draw Call 的工作上了。
那么,一個(gè)很顯然的優(yōu)化想法就是把很多小的DrawCall 合并成一個(gè)大的Draw Call ,這就是批處理的思想。
需要注意的是,由于我們需要在CPU 的內(nèi)存中合并網(wǎng)格,而合并的過程是需要消耗時(shí)間的。因此,批處理技術(shù)更加適合于那些靜態(tài)的物體,例如不會(huì)移動(dòng)的大地、石頭等,對于這些靜態(tài)物體我們只需要合并一次即可。
當(dāng)然,我們也可以對動(dòng)態(tài)物體進(jìn)行批處理。但是,由于這些物體是不斷運(yùn)動(dòng)的,因此每一幀都需要重新進(jìn)行合并然后再發(fā)送給GPU,這對空間和時(shí)間都會(huì)造成一定的影響。

在游戲開發(fā)過程中,為了減少Draw Call 的開銷,需要注意:
避免使用大量很小的網(wǎng)格。當(dāng)不可避免地需要使用很小的網(wǎng)格結(jié)構(gòu)時(shí),考慮是否可以合并它們。
避免使用過多的材質(zhì)。盡量在不同的網(wǎng)格之間共用同一個(gè)材質(zhì)。
合并的網(wǎng)格會(huì)在一次渲染任務(wù)中進(jìn)行繪制,他們的渲染數(shù)據(jù),渲染狀態(tài)和shader都是一樣的,因此合并的條件至少是:同材質(zhì)、同貼圖、同shader。最好網(wǎng)格頂點(diǎn)格式也一致。
合并本身有消耗,因此盡量在編輯器下進(jìn)行合并。
確實(shí)需要在運(yùn)行時(shí)合并的,將靜態(tài)的物體和動(dòng)態(tài)的物體分開合并:靜態(tài)的合并一次就可以,動(dòng)態(tài)的只要有物體發(fā)生變換就要重新合并。
Draw Call 作為游戲性能的關(guān)鍵指標(biāo),優(yōu)化的核心從來不是讓GPU 少畫,而是讓 CPU 少發(fā)命令。掌握批處理技巧,再注意開發(fā)中的細(xì)節(jié),就能有效減少卡頓,讓游戲畫面更流暢。
-
cpu
+關(guān)注
關(guān)注
68文章
11229瀏覽量
223215 -
gpu
+關(guān)注
關(guān)注
28文章
5118瀏覽量
134551
發(fā)布評論請先 登錄

游戲卡頓元兇竟然是 Draw Call!
評論