
1. 900個DrawCall背后的性能悖論第一次看到這個數(shù)據(jù)的時候我盯著Profiler面板愣了好幾秒。DrawCall計數(shù)明明白白寫著900但GPU耗時只有2.3毫秒CPU渲染線程耗時也不過4.1毫秒。按照我過去積累的經(jīng)驗(yàn)這個數(shù)量的DrawCall放在大多數(shù)項目里光是提交渲染命令的開銷就足以讓幀率掉到30以下。但眼前這個場景跑在穩(wěn)定的120幀畫面里該有的東西一樣不少。這個現(xiàn)象之所以值得拿出來聊是因?yàn)樗苯犹魬?zhàn)了很多開發(fā)者腦子里那條根深蒂固的公式DrawCall高等于性能差。我見過太多項目在優(yōu)化階段把DrawCall數(shù)量當(dāng)成唯一的KPI美術(shù)被要求瘋狂合批程序被要求把能合并的材質(zhì)全合并結(jié)果DrawCall是降下來了但幀率紋絲不動甚至因?yàn)楹吓鷮?dǎo)致的紋理圖集膨脹反而讓內(nèi)存和帶寬吃了虧。問題的關(guān)鍵在于DrawCall數(shù)量本身只是一個計數(shù)指標(biāo)它不直接等于性能開銷。真正決定渲染耗時的是這個數(shù)字背后隱藏的一整套運(yùn)行時行為每次DrawCall觸發(fā)的狀態(tài)切換成本、提交命令時的CPU端開銷、GPU端實(shí)際執(zhí)行的像素和頂點(diǎn)工作量、以及驅(qū)動層面對這些命令的調(diào)度效率。900個DrawCall耗時不高說明這個場景在這些維度上恰好都踩在了比較理想的位置。這篇文章適合兩類人看。一類是正在做性能優(yōu)化、被DrawCall數(shù)量困擾的開發(fā)者另一類是對渲染管線底層機(jī)制感興趣、想搞清楚“為什么數(shù)字和體感對不上”的技術(shù)美術(shù)。我會從DrawCall的真實(shí)成本構(gòu)成講起拆解這個場景可能具備的特征然后給出可復(fù)現(xiàn)的驗(yàn)證方法和優(yōu)化思路。全程不堆砌術(shù)語盡量用實(shí)際項目里能直接上手的方式來說。2. DrawCall的真實(shí)成本到底由什么構(gòu)成2.1 每次DrawCall的固定開銷與可變開銷很多人把DrawCall理解成一個“提交一次繪制命令”的動作這個理解沒錯但太粗了。一次DrawCall從CPU發(fā)起到GPU執(zhí)行完畢中間經(jīng)過的環(huán)節(jié)遠(yuǎn)比想象中多。CPU端要做的事情包括準(zhǔn)備渲染狀態(tài)、綁定著色器和常量緩沖區(qū)、設(shè)置頂點(diǎn)和索引緩沖、調(diào)用圖形API的繪制函數(shù)、驅(qū)動層把這些命令翻譯成GPU能識別的指令包。GPU端則要經(jīng)歷命令解析、狀態(tài)切換、圖元裝配、光柵化、像素著色、輸出合并這一整套流程。這里面有一部分開銷是固定的不管你畫的是一個三角形還是一百萬個三角形每次DrawCall都要走一遍。比如狀態(tài)驗(yàn)證、命令打包、驅(qū)動層的參數(shù)檢查。另一部分開銷是可變的取決于這次繪制涉及多少頂點(diǎn)、多少像素、用了多復(fù)雜的著色器。900個DrawCall耗時不高第一個可能的原因就是每次DrawCall的固定開銷被壓得很低。這通常意味著幾件事渲染狀態(tài)切換極少、著色器變體統(tǒng)一、常量緩沖區(qū)更新方式高效、驅(qū)動層沒有做多余的驗(yàn)證工作。換句話說這900個DrawCall很可能在狀態(tài)組織上非常規(guī)整不是那種“每個物體一套獨(dú)立材質(zhì)、每次繪制都要重新綁定一堆資源”的散亂結(jié)構(gòu)。2.2 狀態(tài)切換才是真正的隱形殺手我做過一個對比測試在同一個場景里用兩種方式組織渲染第一種是900個DrawCall每個DrawCall使用相同的著色器和材質(zhì)參數(shù)只是頂點(diǎn)數(shù)據(jù)不同第二種是300個DrawCall但每次繪制之間都要切換著色器、切換紋理、切換混合模式。結(jié)果第一種的CPU渲染耗時反而比第二種低了將近40%。這個測試說明了一個被很多人忽略的事實(shí)DrawCall數(shù)量本身的影響遠(yuǎn)不如狀態(tài)切換次數(shù)來得大。現(xiàn)代圖形API和驅(qū)動層對連續(xù)相同狀態(tài)的DrawCall有很好的批處理優(yōu)化驅(qū)動可以把這些命令打包成一個批次提交給GPUGPU也可以連續(xù)執(zhí)行而不需要等待狀態(tài)更新。但一旦狀態(tài)發(fā)生變化驅(qū)動就要插入同步點(diǎn)、刷新管線、重新配置硬件單元這個開銷可能是單純提交一次繪制的幾十倍。所以當(dāng)你看到900個DrawCall耗時不高時大概率這個場景的渲染狀態(tài)組織得非常緊湊。可能所有不透明物體共用同一個著色器變體紋理通過紋理數(shù)組或者綁定數(shù)組的方式統(tǒng)一管理常量緩沖區(qū)按批次更新而不是逐個物體更新。這種組織方式讓驅(qū)動和GPU都能跑在比較順暢的流水線上。2.3 驅(qū)動層與API的批處理能力不同圖形API對DrawCall的處理效率差異很大。傳統(tǒng)的圖形API在驅(qū)動層做了大量狀態(tài)驗(yàn)證和錯誤檢查每次DrawCall的CPU開銷相對較高。而新一代圖形API把很多驗(yàn)證工作交給了開發(fā)者驅(qū)動層更薄命令提交的路徑更短同樣數(shù)量的DrawCallCPU開銷可以低很多。另外驅(qū)動本身也會做優(yōu)化。比如當(dāng)它檢測到連續(xù)多個DrawCall使用相同的管線狀態(tài)時會自動把它們合并成一個內(nèi)部批次。當(dāng)檢測到常量緩沖區(qū)更新頻繁時會使用環(huán)形緩沖區(qū)來避免GPU等待。這些優(yōu)化在驅(qū)動內(nèi)部默默發(fā)生開發(fā)者看不到但效果直接體現(xiàn)在耗時上。900個DrawCall耗時不高很可能這個項目使用的圖形API和驅(qū)動組合恰好讓這些優(yōu)化充分發(fā)揮了作用。如果換成另一個API或者另一個驅(qū)動版本同樣的場景可能完全是另一個結(jié)果。這也是為什么性能優(yōu)化不能只看數(shù)字必須結(jié)合具體運(yùn)行環(huán)境來判斷。3. 900個DrawCall耗時低的幾種合理解釋3.1 場景本身以簡單幾何體為主第一個需要排查的方向是場景的幾何復(fù)雜度。如果這900個DrawCall繪制的都是簡單的四邊形、粒子、UI元素或者低面數(shù)模型那么GPU端的頂點(diǎn)處理和光柵化工作量會非常小。每個DrawCall可能只畫幾十個頂點(diǎn)、覆蓋幾百個像素GPU幾乎瞬間就能完成。我見過一個典型的例子是2D粒子系統(tǒng)。每個粒子單獨(dú)一個DrawCall數(shù)量輕松上到幾百甚至上千但因?yàn)槊總€粒子就是一個四邊形、四個頂點(diǎn)、兩個三角形GPU處理起來毫無壓力。這種情況下DrawCall數(shù)量雖然高但GPU耗時可能只有零點(diǎn)幾毫秒。真正需要擔(dān)心的是CPU端提交命令的開銷但如果粒子系統(tǒng)用了實(shí)例化或者間接繪制連這個開銷也被攤薄了。判斷方法很簡單在Profiler里看GPU耗時和頂點(diǎn)/像素輸出量。如果頂點(diǎn)數(shù)和像素數(shù)都很低那DrawCall數(shù)量高就不是問題。如果頂點(diǎn)數(shù)很高但耗時仍然低那說明GPU的頂點(diǎn)處理能力很強(qiáng)或者頂點(diǎn)著色器極其簡單。3.2 渲染狀態(tài)高度一致驅(qū)動合并效率高第二個方向是檢查渲染狀態(tài)的切換頻率。如果這900個DrawCall在提交時著色器程序、紋理綁定、混合狀態(tài)、深度測試模式這些關(guān)鍵狀態(tài)幾乎沒有變化那么驅(qū)動層可以把它們當(dāng)作一個連續(xù)的批次來處理。GPU不需要在繪制之間等待管線刷新可以一直保持滿負(fù)荷運(yùn)轉(zhuǎn)。這種場景在實(shí)際項目中是存在的。比如一個使用紋理數(shù)組的地形渲染系統(tǒng)所有地塊共用同一個著色器紋理通過數(shù)組索引區(qū)分常量緩沖區(qū)按地塊批次更新。900個地塊就是900個DrawCall但狀態(tài)切換幾乎為零。驅(qū)動看到的就是一長串參數(shù)略有不同的相同命令處理起來非常高效。驗(yàn)證方法是抓取一幀的API調(diào)用序列統(tǒng)計狀態(tài)切換的次數(shù)。如果狀態(tài)切換次數(shù)遠(yuǎn)小于DrawCall數(shù)量那就說明狀態(tài)組織得很好。如果每次DrawCall都伴隨多次狀態(tài)切換那耗時低就另有原因了。3.3 CPU端提交與GPU端執(zhí)行的重疊現(xiàn)代渲染架構(gòu)普遍采用多緩沖和命令隊列機(jī)制CPU提交命令和GPU執(zhí)行命令是并行進(jìn)行的。CPU把命令寫入命令緩沖區(qū)后就可以繼續(xù)處理下一幀的邏輯GPU從隊列里取命令執(zhí)行。只要CPU提交命令的速度跟得上GPU執(zhí)行的速度并且隊列深度足夠那么即使DrawCall數(shù)量較多也不會成為瓶頸。900個DrawCall耗時不高可能意味著CPU提交這些命令的總時間小于GPU執(zhí)行一幀的時間整個管線處于GPU受限而不是CPU受限的狀態(tài)。這種情況下DrawCall數(shù)量還有繼續(xù)增加的空間直到CPU提交時間超過GPU執(zhí)行時間為止。這個判斷可以通過Profiler里的CPU和GPU耗時對比來做。如果GPU耗時明顯高于CPU渲染線程耗時說明瓶頸在GPU端DrawCall數(shù)量不是問題。如果兩者接近說明CPU提交已經(jīng)接近極限再增加DrawCall就會開始拖慢幀率。3.4 實(shí)例化與間接繪制的隱性貢獻(xiàn)還有一個容易被忽略的因素是實(shí)例化和間接繪制。有些引擎在統(tǒng)計DrawCall時會把一次實(shí)例化繪制算作一個DrawCall但實(shí)際上這次繪制可能包含了成百上千個實(shí)例。這種情況下900個DrawCall背后可能是幾十萬個實(shí)際繪制的物體但每個DrawCall的提交成本被大量實(shí)例分?jǐn)偭?。間接繪制也是類似的情況。GPU通過間接緩沖區(qū)自己讀取繪制參數(shù)CPU只需要提交一次間接繪制命令GPU就會根據(jù)緩沖區(qū)里的參數(shù)執(zhí)行多次繪制。這種機(jī)制下DrawCall的統(tǒng)計口徑和實(shí)際繪制次數(shù)可能完全對不上。所以看到900這個數(shù)字時先確認(rèn)一下統(tǒng)計口徑。如果包含了實(shí)例化和間接繪制那這個數(shù)字的實(shí)際含義和傳統(tǒng)意義上的DrawCall可能差別很大。4. 如何驗(yàn)證你的場景是否屬于“高DrawCall低耗時”類型4.1 用Profiler拆解CPU與GPU耗時驗(yàn)證的第一步是打開引擎自帶的Profiler把一幀的耗時拆開看。重點(diǎn)看三個數(shù)字CPU主線程耗時、CPU渲染線程耗時、GPU耗時。如果CPU渲染線程耗時遠(yuǎn)小于GPU耗時說明CPU提交命令不是瓶頸DrawCall數(shù)量還有余量。如果CPU渲染線程耗時接近甚至超過GPU耗時說明CPU端已經(jīng)在滿負(fù)荷工作DrawCall數(shù)量接近臨界點(diǎn)。我通常還會看渲染線程里各個子階段的時間分布。比如場景剔除、渲染狀態(tài)排序、命令提交、驅(qū)動內(nèi)部處理這幾個階段各占多少。如果命令提交和驅(qū)動處理占比很低說明DrawCall的提交效率很高。如果這兩個階段占比很高那即使總耗時不高也說明優(yōu)化空間還在。4.2 抓取一幀的API調(diào)用序列更深入的方法是抓取一幀的圖形API調(diào)用序列。很多平臺提供了API抓取工具可以記錄一幀內(nèi)所有的繪制調(diào)用、狀態(tài)設(shè)置、資源綁定操作。把這份記錄導(dǎo)出來統(tǒng)計幾個關(guān)鍵指標(biāo)DrawCall總數(shù)、狀態(tài)切換次數(shù)、著色器切換次數(shù)、紋理綁定次數(shù)、常量緩沖區(qū)更新次數(shù)。如果狀態(tài)切換次數(shù)遠(yuǎn)小于DrawCall數(shù)量說明狀態(tài)組織得很好。如果每次DrawCall都伴隨多次狀態(tài)切換那就要分析這些切換是否必要。很多時候狀態(tài)切換是因?yàn)殇秩九判驔]做好把相同材質(zhì)的物體分散到了不同的渲染批次里。4.3 逐步增加DrawCall數(shù)量觀察耗時變化還有一個簡單粗暴但很有效的方法人為增加DrawCall數(shù)量觀察耗時如何變化??梢栽趫鼍袄镏鸩皆黾酉嗤馁|(zhì)的簡單物體每次增加100個DrawCall記錄CPU和GPU耗時的變化曲線。如果耗時隨DrawCall數(shù)量線性增長且斜率很小說明每次DrawCall的邊際成本很低系統(tǒng)還有很大的承載空間。如果耗時在某一點(diǎn)突然跳升說明觸發(fā)了某個瓶頸可能是命令緩沖區(qū)滿了、狀態(tài)緩存失效了、或者驅(qū)動進(jìn)入了慢路徑。這個測試能幫你找到當(dāng)前場景的DrawCall容量上限也能驗(yàn)證耗時低是因?yàn)橄到y(tǒng)效率高還是因?yàn)檫€沒到瓶頸點(diǎn)。5. 高DrawCall場景下的優(yōu)化取舍與實(shí)操建議5.1 不要盲目追求降低DrawCall數(shù)量很多優(yōu)化文檔把降低DrawCall數(shù)量當(dāng)成金科玉律但實(shí)際項目里降低DrawCall數(shù)量往往意味著要做合批而合批是有代價的。靜態(tài)合批會增加內(nèi)存占用和包體大小動態(tài)合批會增加CPU端的頂點(diǎn)變換開銷GPU實(shí)例化要求物體使用相同的網(wǎng)格和材質(zhì)。如果這些代價換來的性能提升還不如DrawCall數(shù)量降低帶來的收益那這個優(yōu)化就是負(fù)面的。我的建議是先把DrawCall數(shù)量放到一邊用Profiler確認(rèn)當(dāng)前瓶頸到底在哪里。如果瓶頸在GPU的像素填充率那降低DrawCall數(shù)量毫無幫助。如果瓶頸在CPU的邏輯更新那渲染端的優(yōu)化也解決不了問題。只有當(dāng)確認(rèn)瓶頸在CPU的渲染命令提交時才需要考慮降低DrawCall數(shù)量。5.2 優(yōu)先優(yōu)化狀態(tài)切換而不是DrawCall數(shù)量如果確認(rèn)渲染提交是瓶頸第一優(yōu)先級的優(yōu)化目標(biāo)應(yīng)該是減少狀態(tài)切換而不是減少DrawCall數(shù)量。具體做法包括按材質(zhì)和著色器對渲染對象排序讓相同狀態(tài)的物體連續(xù)繪制使用紋理數(shù)組或綁定數(shù)組來減少紋理切換把多個常量緩沖區(qū)的更新合并成一次批量更新避免在渲染過程中動態(tài)創(chuàng)建或銷毀資源。這些優(yōu)化做下來即使DrawCall數(shù)量沒有明顯下降CPU渲染耗時也可能大幅降低。我經(jīng)歷過一個項目DrawCall從1200降到1100但狀態(tài)切換次數(shù)從8000降到1500CPU渲染耗時直接砍半。這比單純降DrawCall數(shù)量有效得多。5.3 合理利用實(shí)例化和間接繪制對于大量重復(fù)物體的場景實(shí)例化和間接繪制是降低CPU提交開銷的利器。實(shí)例化讓一次DrawCall可以繪制多個物體間接繪制讓GPU自己決定繪制參數(shù)。兩者結(jié)合使用可以把CPU從繁重的命令提交工作中解放出來。但實(shí)例化也有適用條件。它要求所有實(shí)例使用相同的網(wǎng)格和材質(zhì)如果物體之間差異較大實(shí)例化的收益就會下降。間接繪制則要求繪制參數(shù)在GPU端可見需要額外的緩沖區(qū)管理。在實(shí)際項目中我通常會把場景里的物體按網(wǎng)格和材質(zhì)分組對每組內(nèi)數(shù)量超過一定閾值的物體啟用實(shí)例化剩下的走普通繪制路徑。5.4 關(guān)注驅(qū)動版本和圖形API的選擇不同驅(qū)動版本對DrawCall的處理效率可能有明顯差異。有時候升級一次驅(qū)動同樣的場景CPU渲染耗時就能降低百分之二三十。圖形API的選擇也很關(guān)鍵新一代API在命令提交效率上通常優(yōu)于傳統(tǒng)API但對開發(fā)者的要求也更高。如果項目允許可以在目標(biāo)平臺上對比測試不同圖形API的渲染耗時。有些平臺對特定API有更好的驅(qū)動優(yōu)化切換API可能帶來意想不到的收益。但這個決策要謹(jǐn)慎因?yàn)锳PI切換可能影響渲染效果和兼容性需要做充分的回歸測試。6. 從900這個數(shù)字反推渲染架構(gòu)的合理性6.1 900個DrawCall對應(yīng)的場景規(guī)模判斷900個DrawCall在當(dāng)前的渲染架構(gòu)下對應(yīng)的場景規(guī)??梢杂泻艽蟛町悺H绻且苿佣隧椖?00個DrawCall已經(jīng)算是比較高的數(shù)字通常意味著場景里有大量獨(dú)立物體或者粒子效果。如果是PC端項目900個DrawCall屬于中等偏下的水平很多3A級場景的DrawCall數(shù)量在幾千甚至上萬。判斷合理性不能只看數(shù)字要結(jié)合目標(biāo)平臺和幀率要求。移動端60幀的目標(biāo)下900個DrawCall如果耗時不高說明這個項目的渲染架構(gòu)針對移動平臺做了很好的優(yōu)化。PC端120幀的目標(biāo)下900個DrawCall耗時低是正常水平說明架構(gòu)沒有明顯問題。6.2 渲染排序策略對耗時的影響渲染排序策略直接影響狀態(tài)切換次數(shù)和DrawCall的提交效率。常見的排序策略有按材質(zhì)排序、按深度排序、按渲染隊列排序。按材質(zhì)排序能最大程度減少狀態(tài)切換但可能導(dǎo)致過度繪制增加。按深度排序能減少過度繪制但狀態(tài)切換次數(shù)會增加。900個DrawCall耗時低說明這個項目在排序策略上找到了比較好的平衡點(diǎn)??赡苁窍劝翠秩娟犃蟹纸M組內(nèi)按材質(zhì)排序材質(zhì)相同的再按深度排序。這種多級排序策略能在狀態(tài)切換和過度繪制之間取得較好的折中。6.3 多線程渲染的貢獻(xiàn)現(xiàn)代引擎普遍支持多線程渲染把渲染命令的生成和提交分配到多個線程上。主線程負(fù)責(zé)邏輯更新和可見性剔除渲染線程負(fù)責(zé)生成渲染命令RHI線程負(fù)責(zé)提交命令到GPU。這種架構(gòu)下DrawCall的提交開銷被多個線程分?jǐn)倖尉€程的壓力大大降低。900個DrawCall耗時不高可能得益于多線程渲染架構(gòu)。渲染線程和RHI線程并行工作CPU端的提交時間被壓縮到很短。這種情況下即使DrawCall數(shù)量繼續(xù)增加只要不超過線程間的同步開銷耗時也不會明顯上升。6.4 這個案例對渲染架構(gòu)設(shè)計的啟示這個案例給我的最大啟示是渲染架構(gòu)的設(shè)計目標(biāo)應(yīng)該是讓GPU保持忙碌而不是讓某個計數(shù)指標(biāo)好看。DrawCall數(shù)量、狀態(tài)切換次數(shù)、頂點(diǎn)數(shù)、像素數(shù)這些都是手段不是目的。真正重要的是幀率穩(wěn)定、耗時可控、在不同場景下都有可預(yù)測的表現(xiàn)。一個合理的渲染架構(gòu)應(yīng)該具備幾個特征狀態(tài)組織緊湊、排序策略靈活、支持實(shí)例化和間接繪制、能充分利用多線程、對驅(qū)動和API的特性有針對性利用。做到這些DrawCall數(shù)量高一點(diǎn)低一點(diǎn)都不會成為問題。做不到這些即使DrawCall數(shù)量降到很低性能也可能不理想。7. 我在實(shí)際項目中踩過的相關(guān)坑7.1 把DrawCall數(shù)量當(dāng)成唯一優(yōu)化指標(biāo)早期做項目的時候我把DrawCall數(shù)量當(dāng)成渲染優(yōu)化的唯一KPI要求美術(shù)和程序把能合并的都合并。結(jié)果一個場景的DrawCall從800降到了300但幀率沒有任何提升反而因?yàn)楹吓鷮?dǎo)致的內(nèi)存增長讓加載時間變長了。后來用Profiler一分析發(fā)現(xiàn)瓶頸一直在GPU的像素填充率上跟DrawCall數(shù)量毫無關(guān)系。這個教訓(xùn)讓我明白優(yōu)化必須從實(shí)際瓶頸出發(fā)不能憑經(jīng)驗(yàn)拍腦袋。DrawCall數(shù)量只是一個參考指標(biāo)它高不一定有問題低也不一定沒問題。關(guān)鍵是要看它是否成為了瓶頸。7.2 忽略狀態(tài)切換導(dǎo)致的性能抖動還有一個坑是只關(guān)注DrawCall總數(shù)忽略了狀態(tài)切換的分布。有一次優(yōu)化后DrawCall總數(shù)沒變但幀率變得很不穩(wěn)定時而流暢時而卡頓。排查了很久才發(fā)現(xiàn)優(yōu)化過程中調(diào)整了渲染排序策略導(dǎo)致某些幀的狀態(tài)切換次數(shù)突然暴增觸發(fā)了驅(qū)動的慢路徑。狀態(tài)切換的分布比總數(shù)更重要。均勻分布的狀態(tài)切換可以被驅(qū)動平滑處理集中爆發(fā)的狀態(tài)切換則會導(dǎo)致明顯的性能抖動。優(yōu)化時不僅要看總數(shù)還要看每幀的分布情況。7.3 在不同平臺上套用相同的優(yōu)化策略移動端和PC端的渲染架構(gòu)差異很大同樣的優(yōu)化策略在兩個平臺上可能效果完全相反。我在移動端做過一個優(yōu)化把大量小物體合并成一個大網(wǎng)格DrawCall數(shù)量大幅下降移動端幀率提升明顯。但同樣的策略放到PC端因?yàn)镻C端GPU的頂點(diǎn)處理能力更強(qiáng)合批帶來的CPU端頂點(diǎn)變換開銷反而成了新瓶頸幀率不升反降。優(yōu)化策略必須針對目標(biāo)平臺定制。移動端GPU的帶寬和填充率是主要瓶頸合批和減少狀態(tài)切換通常有效。PC端GPU的頂點(diǎn)和像素處理能力都很強(qiáng)CPU端的提交開銷更容易成為瓶頸優(yōu)化重點(diǎn)應(yīng)該放在減少CPU端工作量上。7.4 忽視驅(qū)動版本和硬件差異同一個項目在不同驅(qū)動版本和不同硬件上的表現(xiàn)可能差異很大。我遇到過一個問題在開發(fā)機(jī)上DrawCall耗時很低到了測試機(jī)上耗時翻倍。排查后發(fā)現(xiàn)是測試機(jī)的驅(qū)動版本較舊對某些渲染狀態(tài)的驗(yàn)證邏輯更嚴(yán)格導(dǎo)致每次DrawCall的CPU開銷更高。性能優(yōu)化不能只在開發(fā)機(jī)上驗(yàn)證必須在目標(biāo)硬件和目標(biāo)驅(qū)動版本上做充分測試。有條件的話應(yīng)該建立一個硬件矩陣覆蓋主要的目標(biāo)配置確保優(yōu)化效果在不同環(huán)境下都成立。8. 給遇到類似情況的開發(fā)者的幾條實(shí)用建議如果你也遇到了DrawCall數(shù)量高但耗時不高的情況先別急著下結(jié)論說“沒問題”。用Profiler確認(rèn)一下當(dāng)前的瓶頸到底在哪里是CPU提交、GPU執(zhí)行、還是別的什么環(huán)節(jié)。如果確認(rèn)瓶頸不在渲染提交上那DrawCall數(shù)量確實(shí)不是當(dāng)前需要關(guān)注的問題可以把精力放到真正的瓶頸上。如果你確認(rèn)瓶頸在渲染提交上但DrawCall數(shù)量已經(jīng)很難再降那就把優(yōu)化重點(diǎn)轉(zhuǎn)向狀態(tài)切換和提交效率。檢查渲染排序策略是否合理狀態(tài)切換是否集中常量緩沖區(qū)更新是否高效驅(qū)動和API是否有優(yōu)化空間。這些方面的優(yōu)化往往比單純降DrawCall數(shù)量更有效。還有一點(diǎn)很重要不要在不同平臺上套用相同的優(yōu)化策略。移動端和PC端的瓶頸點(diǎn)不同優(yōu)化手段也應(yīng)該不同。在移動端有效的合批策略到了PC端可能適得其反。做優(yōu)化決策前先搞清楚目標(biāo)平臺的硬件特性和驅(qū)動行為。最后保持對數(shù)據(jù)的敏感但不要迷信數(shù)據(jù)。DrawCall數(shù)量、狀態(tài)切換次數(shù)、頂點(diǎn)數(shù)、像素數(shù)這些都是參考真正重要的是幀率是否穩(wěn)定、耗時是否可控、玩家體驗(yàn)是否流暢。數(shù)字服務(wù)于體驗(yàn)而不是反過來。