Unity5內部渲染的最佳化2:清理,unity5渲染

來源:互聯網
上載者:User

Unity5內部渲染的最佳化2:清理,unity5渲染

譯自aras的部落格,總共3篇文章,講述unity5最佳化自己渲染器的過程
吸取大神調試與最佳化經驗,瞭解unity5內部渲染器的最佳化方法

前篇:Unity5內部渲染的最佳化1:介紹


介紹過去後,讓我們來進行實際工作

 
在以前的文章已經提到的,首先我嘗試想起/找出現有代碼,做一些分析並且寫下突出的地方。    
分析多重專案主要揭示了兩件事:
1.    渲染代碼使用多線程真的比使用我們現有的“一個主線程和一個渲染線程” 更廣闊。這裡有一個從unity5的timeline profiler的截屏:
 
在這種特殊情況下, CPU的瓶頸是渲染線程,大部分的時間都花費在了 glDrawElements(這在MacBookPro; 在蝴蝶效應這個demoGPU簡化情境處理了6000drawcall)。主線程只是結束等待渲染線程之後再追上。依靠硬體,平台,圖形API等。瓶頸能在任何地方出現,比如 相同的程式在更快的PC在DX11下 主線程和渲染相比消耗的時間相同。
culling sliver在似乎不錯,最終我們希望我們所有渲染代碼都能那樣不錯。這裡放大剔除部分:
 
2.沒有“最佳化這一個函數,使所有事情都兩倍快”的地方:(   重排資料將是一個漫長的旅程,移除多餘的決策,移除這裡和那裡的小的事情,直到我們能達到“每個線程兩倍快”。如果。
渲染線程分析的資料並不是特別有趣。大部分的時間(下面突出加亮顯示的一切) 是OpenGL運行已耗用時間/驅動。添加一些注釋關於我們做了什麼蠢事情而導致驅動做了太多多餘的事情(我不知道, 沒有好的原因切換不同的頂點布局等),但是另外在我們的方面沒有看太多。大部分剩餘時間都消耗在動態批處理上了
 
看看在主線程消耗非常大的函數,我們得到了這些:
 
現在當然有問題產生(為什麼這麼多雜湊表尋找?為什麼排序要這麼長時間?等等,看看上面的列表),但關鍵是,沒有在一個地方最佳化什麼就能帶來魔法效能效益和一匹小馬(此處比喻)。
觀察1:材質“顯示列表”總是被重建在我們的代碼中,在渲染線程中一個材質能預先重新記錄我們叫做一個“顯示列表”。認為他是一個小的命令緩衝,儲存一群指令(“設定光柵狀態物件,設定這個著色器,這隻這些貼圖”)。關於他們的最重要的事情:它們儲存了所有參數(最終貼圖的值,shader uniform變數值,等等)。當“applying”一個顯示列表,我們只需要把它切換出渲染線程,不需要 找出材質屬性值或其他事情
一切都很好,除了當在材質中一些事情改變了使得記錄的顯示列表無效。在unity中,每個shader的內部經常有很多shader的變形種類,並且當選擇一個不同的shader變形,我們需要請求一個不同的顯示列表。如果使用一種方式情境被建立引起相同的材質去交替不同的列表,然後我們就產生了問題。
在這幾個標準的項目中到底發生了什麼問題;簡短的故事“在forward渲染的多個逐像素光源是導致這種事情的原因”,結果是我們有代碼來處理這個分支,它只是需要被完成---所以我找到了它,在當前程式碼程式庫編譯它非常奏效。現在的材質能預記錄不止一個“顯示列表”,這個問題也消失了。
PC(酷睿i7 5820 k), 在主線程一個情境從9.52ms到7.25ms是相當可怕的。
劇透:這一變化最大的好處是在受影響的情境,從我做了每件事幾乎用了兩個星期。那個代碼甚至不是我“寫的”,我只是從一些被忽視的分支裡得到的。所以,耶!一個很簡單的改變使得效能提升了30%!
觀察2:雜湊表尋找太多了從上面的觀察名單,,發現“為什麼雜湊表尋找如此之多”的問題。
在渲染代碼中,很多年前我加了一句像這樣的:
Material::SetPassWithShader(Shader* shader, ...)
調用的代碼已經知道了哪個shader應該被設立。材質也知道這個shader,但是它儲存了一些東西我們叫做PPtr(“persistent pointer固執指標”)它本質上是一個控制代碼。指標直接避免做一個控制代碼指向指標(handle->pointer)的尋找(目前是一個雜湊表尋找,由於各種複雜的原因,很難做一個基於數組的處理系統)
結果是,在許多改變之後,Material::SetPassWithShader完成了兩次handle->pointer尋找,即使它已經有了實際的著色器指標作為參數!修複:
 
翻譯:
通過應用程式清理和最佳化材質。
SetPassWithShader在某些點被增加來避免一個PPtr deref。結果是,現在它還是做“兩次”m_shader PPtr derefs!那不是一個糟糕的最佳化。
所以把它們都清理掉;有一個Material.SeShaderPass 它直接取得它需要的,避免廢棄並且到subshader中去。並只是在pass指標基於直接緩衝顯示列表。這允許移除特殊的情況為陰影caster passes複製代碼。
VikingVillageStatic 工作台項目,i7 5820k,主線程 Camera.Render 7.25ms->6.59ms 17個檔案改變,有135個插入的地方和213個刪除的地方:
Ok我們的結果是好的,可以衡量的並且非常簡單的實現了效能最佳化,也是的程式碼程式庫更加小巧,多麼好的一件事情。
小的調整在上面Mac的渲染線程效能分析中,我們自己的代碼在 BindDefaultVertexArray消耗了2.3%,感覺消耗得太多了。結果,它迴圈所有可能的頂點組件類型並檢查一些東西。使得使用shader代碼迴圈只在頂點組件部分。稍微快了一些
一個項目使用GetTextureDecodeValues很多次,它是用來計算色彩空間的,HDR和光照貼圖紋理解壓縮為常數。一個可選“intensity multiplier”參數,在所有的調用地方都被明確的設定為1.0,只有一個除外,它做了一系列的sRGB數學操作 。我察覺到,在代碼中使得一些pow()調用消失了 。增加到一個“look later” 清單中:為什麼在第一個地方我們調用這個函數這麼頻繁呢?
一些代碼在渲染迴圈中計算出drawcall對邊界批處理需要放在哪裡  (也就是說:去哪裡切換到一個新的shader,等等),是比較一些狀態作為單獨的bool。把它們打包到位域中並且比較一個整數。沒有可觀察到效能變好,但是實際上代碼變少了,所以一場勝利:)
(位域是指資訊在儲存時,並不需要佔用一個完整的位元組, 而只需占幾個或一個二進位位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。所謂“位域”是把一個位元組中的二進位劃分為幾 個不同的地區, 並說明每個地區的位元。每個域有一個網域名稱,允許在程式中按網域名稱進行操作。 這樣就可以把幾個不同的對象用一個位元組的二進位位域來表示。)
注意到,弄清楚哪個頂點緩衝和頂點布局被物體查詢網格資料使用,在記憶體中是一個非常遠的部分。基於用途類型重排資料(渲染資料,碰撞資料,動畫資料等等)
也減少了資料封裝漏洞msinilo的優秀的CruncherSharp (在這種方法上做了一些調整:))(為CruncherSharp )我聽說有一個小工具對Linux(pahole)。在Mac上有struct_layout但是它可以永遠在unity中執行並且Python 指令碼經常有一些溢出異常引起失敗。
 
瀏覽代碼的時候,發現 我們追蹤每個貼圖的mipmap bias的方法非常複雜。當紋理跟蹤所有將要使用的的材質屬性工作表時時,它設定每一個紋理;通知它們在任何mip bias上的改變,並且bias 是從效能表和 一起應用的每個紋理中獲得,每次設定一個紋理在圖形驅動上。天啊。固定的。因為這改變了我們圖形抽象API的介面,這意味著改變所有11個渲染後端; 幾個相當微不足道的改變但感覺很恐怖(我甚至連局部的建立它們的一半都不能)。不要害怕,我們有建造場來檢查編譯錯誤,和測試程式組來逆向檢查!
 
翻譯:
使紋理mip bias 健全的處理。
當bias是紋理階段的一部分,它只這樣怪異的,不是紋理。或者也許是其它的東西。
現在它只是紋理過濾器filter/變形warp/各向異性aniso的一部分設定,並且只有當它被改變時才被應用。
 Gone:
Bias應用在每個和所有gfxdevice.SetTexture 調用中,
Texture::NotifyMipBiasChanged,(博主註:先通知mip bias改變了)
TexEnv::TextureMipBiasChanged, (博主註:再調用改變mip bias紋理)
TexEnvData::mipBias。(博主註:再確確實實執行bias操作)
80個檔案改變了,插入了228,刪除了263。
沒有明顯的效能差別,但是感覺不那麼複雜了。加一個“look later”清單:我們追蹤每個紋理有很多相同的資料;有些關於非二次冪限制的紋理(NPOT)的UV縮放。這些天我懷疑它沒有存在的理由,如果可能的話繼續觀察並且移除它。
並且也做了一些其他一些類似的局部調整,它們每個都很簡單,讓一些特殊的地方更好一些,但是不會觀察到任何效能改進。也許把它們放大一百倍才能看到一些明顯的影響,但是它們有更多的可能性  我們需要重做一些要嚴重事情去得到更好的結果。
材質屬性工作表的布局一件一直困擾我的事情是我們怎麼儲存材質屬性。每次我把程式碼程式庫給新程式員看,在那我翻著白眼說:“噢耶,我們在材質中儲存紋理、矩陣、顏色等,在分離的STL map中。令人厭惡。令人厭惡。”。

Map是標準關聯式容器(associative container)之一,一個map是一個鍵值對序列,即(key ,value)對。它提供基於key的快速檢索能力,在一個map中key值是唯一的。map提供雙向迭代器,即有從前往後的(iterator),也有從後往前的(reverse_iterator)。
map要求能對key進行<操作,且保持按key值遞增有序,因此map上的迭代器也是遞增有序的。如果對於元素並不需要保持有序,可以使用hash_map。
http://www.cnblogs.com/skynet/archive/2010/06/18/1760518.html)    
有這個流行的想法認為c++ STL容器在高效能代碼中沒有立足之地, 而且沒有好遊戲使用它(不是真的),如果你使用它你一定很愚蠢並被嘲笑(我不知道…也許?)。所以,嘿,我如何把這些maps 替換為一個更好的資料布局?必須讓一切更好一百萬倍,對嗎?
在Unity中,shader的參數可以來自兩個地方:每個材質的資料,或“全域”材質參數。前者通常是“diffuse texture漫反射紋理”,後者就像“fog color霧的顏色”或者“Camera projection相機的投射”(博主註:漫反射紋理貼圖參數是每個shader中都是特有的,霧的顏色在unity的RenderSettings 每個shader都共有)(每個執行個體的參數都有點複雜MaterialPropertyBlock等等,但是現在先讓我們忽略)
我們之前的資料布局大概是這樣的:

map<PropertyName, float> m_Floats;
map<PropertyName, Vector4f> m_Vectors;
map<PropertyName, Matrix4x4f> m_Matrices;
map<PropertyName, TextureProperty> m_Textures;
map<PropertyName, ComputeBufferID> m_ComputeBuffers;

set<PropertyName> m_IsGammaSpaceTag; // which properties come as sRGB values


我取而代之的是(簡化,只顯示資料成員;dynamic_array很像std::vector,但更加有EASTL風格):


struct NameAndType { PropertyName name; PropertyType type; };

// Data layout:
// - Array of name+type information for lookups (m_Names). Do
//   not put anything else; only have info needed for lookups!
// - Location of property data in the value buffer (m_Offsets).
//   Uses 4 byte entries for smaller data; don't use size_t!
// - Byte buffer with actual property values (m_ValueBuffer).
// - Additional data per-property in m_GammaProps and
//   m_TextureAuxProps bit sets.
//資料布局:
//-數組名只要 名稱+類型就好有(m_Name)資訊來尋找。不要再加別的東西
//只要有關鍵資訊用來查詢就好
//-效能資料的位置在value buffer (m_Offsets)中。使用4byte來記錄小資料
//不要使用size_t!
//- 實際的屬性值(m_ValueBuffer)使用Byte緩衝
//-追加的資料  每個屬性在m_GammaProps和m_TextureAuxProps設定為bit。
//
// All the arrays need to be kept in sync (sizes the same; all
// indexed by the same property index).
//所有數組需要保持同步(大小相同;相同屬性的索引)
dynamic_array<NameAndType> m_Names;
dynamic_array<int> m_Offsets;
dynamic_array<UInt8> m_ValueBuffer;

// A bit set for each property that should do gamma->linear
// conversion when in linear color space
//在一個線性空間時都該做gamma->linear轉換,對於每個屬性用一個bit來設定   
dynamic_bitset m_GammaProps;
// A bit set for each property that is aux for a texture
// (e.g. *_ST for texture scale/tiling)
//  對於每個屬性來說一個bit設定是一個紋理的輔助aux
// (如. *_ST for texture scale/tiling)
dynamic_bitset m_TextureAuxProps;

當一個新屬性被添加到一個屬性工作表,它只是附加於所有的數組。屬性名稱/類型資訊和屬性位置在資料緩衝中保持分離,所以當尋找屬性時,我們甚至不擷取對搜尋本身不需要的資料。
最大的外部變化是在這之前,一個是需要找到一個屬性值並且儲存一個直接指標指向它(被用於預記錄材質顯示列表,在重演它們之前能夠“patch in臨時接入” 全域shader屬性的數值)現在每當改變數組大小都會引起指標無效;所以取而代之,能儲存在指標中的所有的代碼,必須改變去儲存位移offsets在屬性列表中。所以,最後有一些代碼被修改了。
 
尋找屬性已經從一個O(logN)的操作(貼圖尋找)轉變到一個O(N)操作。如果你學過電腦科學你就知道這不是個好事,它是一個典型的taught。然而,我看了各種項目並且找到典型的情況,屬性工作表總共包含5-30個屬性(大多數在10左右);並且一個線性掃描所有尋找的資料緊挨著在記憶體中其他的資料,這與STL map尋找map節點能隨意的遠離其他點作對比並不是那麼糟(如果發生了這件事,每個節點能作為一個CPU緩衝儲存空間丟失)。從幾個不同的項目的效能作分析,其中一部分是“尋找屬性”在PC、膝上型電腦和iPhone上一直很快。
可是這一變化帶來了魔法一樣效能改進的嗎?並沒有,它稍微改善了平均幀時間並且記憶體消耗得更少,特別是當有大量不同材質的時候。但是做了“只是用被打包的數組代替了STL maps ”節能有魔法般的結果嗎?並不是。嗯,至少我不必再邊翻我的眼睛邊把這段代碼展示給別人看了,就是這樣。
效果這個工作大約用了兩周時間(我估計只有75%---其餘花在了其它不相關的修正,代碼檢查等)所有的平台建立並且測試通過的一個狀態;準備好請求。40個提交,135個檔案,大概2000行代碼改變了。
 
翻譯:
大多數時間儲存是從一個更好的gfxdevice 顯示列表的超快取,特別是材質總是選擇不同的關鍵字這種情況(例如,Viking Village這款遊戲在很快的PC 12ms-8.5ms)在其他情況下稍微快一些,但是也不是特別快(結合上面的,在google連結文檔的細節)。一般來說結果的幀率更加穩定,也許由於在運行中很少記憶體配置。
    材質不止一個顯示列表在緩衝儲存空間中
    更好的屬性工作表資料布局(6 std::map-> 3 dynamic_array 和 2 bitsets)。這意味著你不能把指標當做值來儲存;改變了所有代碼來儲存offsets。增加了更多的單位測試和效能表!
    讓 texture mip bias健全的處理,現在它只有 filter/wrap/aniso 參數,替代了被迴圈追蹤並且應用在每個SetTexture調用。
    SetPassWithShader曾做了一個最佳化在某些點上來避免一個PPtr deref,但是現在它總是做兩次derefs!把它清理掉。
    移除世界matrix的設定替換成 light props 在正向迴圈之前統一一致,似乎沒有用
    稍微最佳化了線程顯示列表可修補資料
    OpenGL:稍微最佳化了BindDefaultVertexArray (像GLES)
    1multiplier的情況時增加了特殊的GetTextureDecodeValues  
    根據platform folks,網格緩衝永遠不會再安卓/Tizen泰澤系統上丟失  
    撤回TextureID 到所有32位平台除了ps4
    更多密封的結構體/類打包在某些地方
    從shaderlab上移除了不用的東西(如 MatrixVal)並且在很多地方加了注釋
    雜項:支援-資料包 命令列在發布玩家建立也有爭議,並且在mac/linux上



優越的效能,一個標準項目提升了很多(受影響最多的是“顯示列表被重建”的問題),在pc上運行總時間從11.8ms到8.5ms;在筆記本上運行29.2ms到26.9ms。其他項目也被幹刪了,但改變的甚微(在pc上7.8ms到7.3ms;其他項目在iphone上15.2ms到14.1ms,等等)
大部分的效能改進確實來自兩個地方(顯示列表被重建;避免無用雜湊表尋找)。不確定其他改變的變化-總體感覺他們是改變的最好的,如果只是因為現在我對程式碼程式庫有一個好的理解,並且已經添加了大量的註解注釋 什麼&為什麼。我現在甚至也有很長的列表“這裡那裡怪異的地方需要改進”。
花了我近兩周的時間得到了現在這個結果,這樣值得嗎?很難說。有時我花費了一個星期,但我感覺什麼也沒做,所以比這更好:)
  總體來講我還是不確定“最佳化”是不是我的強項。我想我很擅長只有幾件事:
1.    調試困難問題-調試困難的問題——我能很快想出合理的假設和方法逐個擊破問題。
2.    理解一些變化或一個系統的含義----其他系統將會被影響並列有什麼會/將會有問題互動產生。
3.    關於在程式碼程式庫中事情被其他的事情解決了有很好的環境認識----我經常能找出,幾個人在同一件事情上重疊工作,並且告訴他們“喲,你倆應該協調整合一下”   
這些是對最佳化有用的技能嗎?我不知道。我當然不能兼顧指令延遲和執行連接埠和TLB misses 在我的大腦裡。但是也許如果我練習一下會更好?誰知道呢

不知道下一步該走哪條路,我看到幾條可行的道路:
1.    繼續改進,並且希望他們大多數的效果都很好,單獨幾點可能會失望,因為真的很難權衡
2.    開始放眼更大的地方,找出完全可以避免的很多我們目前完成的工作,即更嚴重的事情是“重塑”結構。
3.    一旦一些清理完成,切換到協助別人的“多線程多材料”的方法。
4.    最佳化太難了!讓我們更多的玩搖滾史密斯直到情況好轉
我想我將和幾個人討論並做更多上面講到的事情。下次見!



博主總結:aras的代碼風格非常好,並且他在寫代碼的時候就有許多注釋,因此最佳化起來很有效率,最佳化的地方也很準確。aras也很善用分析器。。。。總之,看了之後受益匪淺。。希望unity越來越牛x。


待譯。。。

                       ----譯自  wolf96  http://blog.csdn.net/wolf96

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.