下載wordpress建站程序seo網(wǎng)站結(jié)構(gòu)優(yōu)化
學(xué)習(xí)筆記
參考各路知乎大佬文章
首先是對(duì)變體的基本認(rèn)知
概括就是變體是指根據(jù)引擎中上層編寫(UnityShaderLab/UE連連看)中的各種defines情況,根據(jù)不同平臺(tái)編譯成的底層shader,OpenGL-glsl/DX(9-11)-dxbc DX12-dxil/Vulkan-spirv,是打到游戲包里的
在引擎開發(fā)編輯模式下,Unity/UE用戶層寫的是HLSL,根據(jù)引擎選擇的目標(biāo)平臺(tái),編譯底層shader的流程也有區(qū)別。項(xiàng)目打包出來(lái)到目標(biāo)平臺(tái)上,是不會(huì)用開發(fā)時(shí)的HLSL再在目標(biāo)平臺(tái)上實(shí)時(shí)編譯成底層shader的,是在游戲打包時(shí),將目標(biāo)平臺(tái)的所有shader變體(glsl/dxbc/spirv)生成好打包進(jìn)去
并且由于壓縮,變體數(shù)對(duì)于游戲包體的影響可能不是很大
DX9-DX11 dxbc是字節(jié)碼,GPU上跑的機(jī)器碼還需要進(jìn)一步轉(zhuǎn)換成二進(jìn)制碼
DX12(2014年推出) dxil,GPU仍需要轉(zhuǎn)成二進(jìn)制碼,但是多了很重要的PSO Cache流程
OpenGL glsl不需要離線編譯,直接交給GPU驅(qū)動(dòng)編譯成機(jī)器碼。OpenGL 4.1以上可以通過glGetProgramBinary回讀這個(gè)二進(jìn)制碼,省去之后的編譯工作
OpenGL ES 3.0(2012年推出)以上也可以通過glGetProgramBinary回讀廠商的機(jī)器碼
Metal(2014年推出) AIR字節(jié)碼,交給GPU驅(qū)動(dòng)編譯成機(jī)器碼
最后GPU上跑的不是glsl/dxbc/spirv ?GPU上跑的是機(jī)器碼
現(xiàn)在的各種游戲,第一次進(jìn)入游戲時(shí)總會(huì)有一個(gè)編譯著色器的流程,這一步是在做什么?Unity和UE項(xiàng)目做的事是不太一樣的,后面講到PSO時(shí)再提及
首先看看手機(jī)OpenGL ES流程
以O(shè)penGL ES3.0為例,不同的硬件廠商的GPU其機(jī)器碼標(biāo)準(zhǔn)是不一樣的,所以第一次進(jìn)游戲時(shí),需要把hlsl編譯成當(dāng)前手機(jī)硬件的GPU機(jī)器碼,并且可以通過glGetProgramBinary回讀廠商的機(jī)器碼將編譯好的機(jī)器碼存在本地磁盤,下次進(jìn)游戲時(shí)直接從磁盤讀取編譯好的產(chǎn)物
那么有沒有辦法不在游戲第一次打開時(shí),不想等這么久的著色器編譯,可以快速開始游戲呢?有的,隨著廠商的發(fā)展,有抽象出一種中間格式的語(yǔ)言,這種語(yǔ)言機(jī)器友好,編譯非常快速,并且具備跨機(jī)器運(yùn)行的能力
及以下三家及對(duì)應(yīng)的中間語(yǔ)言,如果以后的游戲是用DX12/Vulkan/Metal開發(fā)的,游戲打包時(shí)就可以將shader編譯成對(duì)應(yīng)的中間語(yǔ)言,但是這樣做同樣有問題,那就是這種中間格式的大小比shader源碼大很多
然后看看PC DX(9-11)/12的流程
DX(9-11)的dxbc傳遞給顯卡生成機(jī)器碼
DX12 dxil取代了dxbc
由于dxbc和dxil是互不相通的,所以游戲?yàn)榱酥С植恢С諨X12的老電腦,只能將shader源碼打到游戲包中,實(shí)際根據(jù)用戶電腦是否支持DX12,再編譯成dxbc/dxil,最后再是dxil生成PSO Cache,所以黑猴耗時(shí)長(zhǎng)的部分在源碼到dxbc/dxil這一步
PSO Cache(Pipeline State Object Cache渲染管線狀態(tài)對(duì)象緩存)
Metal和Vulkan中有和PSO對(duì)應(yīng)的概念,但并不叫PSO,只是經(jīng)常用PSO替代稱呼,OpenGL/ES沒有PSO概念
要了解PSO是什么,先回憶一下GPU流水線。繪制一個(gè)物體的整個(gè)流程(pipeline),除開shader,其中的還有很多狀態(tài)設(shè)置,比如是否進(jìn)行透明度混合,混合方式是什么等等。
PSO Cache做的事就是把整個(gè)pipeline生成的機(jī)器碼存下來(lái)
這里的PSO包括了shader和應(yīng)用層設(shè)置渲染狀態(tài)的代碼
PSO和硬件是強(qiáng)綁定的,不同顯卡/顯卡驅(qū)動(dòng)生成的PSO緩存也是不能通用的
OK有了上述認(rèn)知,我們現(xiàn)在知道了現(xiàn)代API(DX12/Metal/Vulkan)提供了在應(yīng)用層cache PSO的功能,針對(duì)不同的平臺(tái),引擎應(yīng)用層會(huì)做相應(yīng)的處理,那么接下來(lái)就可以看一下應(yīng)用層的游戲引擎對(duì)應(yīng)PSO Cache的相關(guān)流程了。
https://zhuanlan.zhihu.com/p/572503905
Unity
先看Unity,Unity6(2024.10.17發(fā)布)之前的版本是沒有PSO Cache的功能的
老版本Unity ?Unity - Manual: Shader loading
是把加載的場(chǎng)景或資源所有的材質(zhì)變體都加載到CPU中的,并且有一個(gè)可自定義大小的CPU空間存所有的變體,首次加載時(shí),創(chuàng)建PSO的流程還是要走,可能會(huì)出現(xiàn)卡頓。創(chuàng)建過一次之后,會(huì)緩存該變體。當(dāng)沒有任何物體引用到某變體時(shí)從CPU和GPU中清掉。
為了提高效率,方案是變體WarmUp和變體收集文件ShaderVariantCollection的組合拳。
可以看出老版本的Unity,是無(wú)法省掉創(chuàng)建PSO的開銷的,所以項(xiàng)目的重點(diǎn)會(huì)在于減少項(xiàng)目變體,剔除掉不用的變體,以及盡可能跑全變體收集文件上。
Unity6+ ?對(duì)應(yīng)UE的Bundle PSO Cache 當(dāng)前只支持(DX12/Metal/Vulkan)
Unity - Scripting API: GraphicsStateCollection
新增PSO工作流,主要的功能在GraphicsStateCollection對(duì)象
流程還是跑游戲,根據(jù)目標(biāo)平臺(tái)緩存本地PSO Cache文件,因?yàn)殚_發(fā)期中材質(zhì)變體可能會(huì)經(jīng)常變動(dòng),所以跑游戲更新cache的思路是和原來(lái)的變體收集文件是一樣的
cache的結(jié)果同樣可以查看包含的變體,以及修改每個(gè)變體關(guān)聯(lián)的渲染狀態(tài)
PSO Cache也需要WarmUp,有同步和異步倆種方法執(zhí)行
UE
UE中的PSO類型,這里主要關(guān)心的是Graphics PSO
UE4
Bundle PSO Cache
首先shader會(huì)在打包時(shí)編譯成字節(jié)碼,這些字節(jié)碼有三種保存形式
1、在項(xiàng)目設(shè)置中,ShareMaterialShaderCode開關(guān)勾選才能走PSO Cache流程,如果沒有勾選,字節(jié)碼會(huì)打包附帶于每個(gè)材質(zhì)變體自身上,這樣影響包體大小,雖然熱更只需考慮增量,但這個(gè)方案大體量一些的項(xiàng)目基本都不會(huì)用
2、勾選,存成ushaderbytecode,UE維護(hù)一個(gè)ShaderCodeLibrary歸檔這些字節(jié)碼,除Metal語(yǔ)言外的所有語(yǔ)言都使用該ShaderCodeLibrary
3、勾選,存成Native(metallib/metalmap),Metal原生ShaderCodeLibrary
后續(xù)沒走PSO Cache的流程,創(chuàng)建PSO時(shí)就讀取對(duì)應(yīng)的字節(jié)碼,然后二次編譯PSO
PSO Cache文件有倆種文件類型:
.upipelinecache類型文件,這種是運(yùn)行游戲時(shí)記錄的 其中不會(huì)直接保存shader代碼(無(wú)論是源碼或者編譯好的機(jī)器碼),也不保存shader路徑,保存的是shader路徑的SHA hash作為索引
.spc(Stable PSO cache)類型文件 穩(wěn)定的緩存信息
存儲(chǔ)預(yù)計(jì)多個(gè)版本中不會(huì)改變的信息,如材質(zhì)名稱,頂點(diǎn)工廠名稱,著色器類型等的描述稱為stable key,UE5 UE4.27用.shk/UE4老版本用.scl.csv文件表示
Bundle PSO Cache的流程
https://dev.epicgames.com/documentation/en-us/unreal-engine/optimizing-rendering-with-pso-caches-in-unreal-engine?application_version=5.4
https://zhuanlan.zhihu.com/p/681319390
總體流程就是
1、打包時(shí)Cook一遍工程,掃使用到的所有材質(zhì)變體,將編譯成的平臺(tái)無(wú)關(guān)的字節(jié)碼存到shaderCodeLibrary,生成.shk文件
2、手機(jī)上跑游戲收集PSO,存到.upipelinecache文件中
增量收集
3、根據(jù).shk文件和.upipelinecache文件,用ShaderPipelineCacheTools命令行生成.spc文件,然后將該.spc文件放到項(xiàng)目中再打包,spc文件會(huì)轉(zhuǎn)換成upipelinecache文件打進(jìn)包中,UE會(huì)整理成對(duì)應(yīng)平臺(tái)的PSOList
4、再次啟動(dòng)游戲,自動(dòng)加載upipelinecache,編譯shader時(shí)使用PSO Caching,收集的是對(duì)應(yīng)GPU上編譯成的機(jī)器碼
5、重復(fù)流程
6、項(xiàng)目材質(zhì)有重大改變時(shí),可能需要重新記錄Cache信息,因?yàn)槔系臎]用到的PSO如果更新后根本沒用到就純浪費(fèi)了
以上是項(xiàng)目打包相關(guān)的相關(guān)流程,接下來(lái)看一下手機(jī)跑游戲時(shí)PSO編譯的流程
首先是三個(gè)關(guān)鍵流程
UE Graphics PSO緩存的信息包括 ?
其中BoundShaderStateInput(BSS)包括
根據(jù)平臺(tái)的不同,上訴信息可能只有部分作為PSO提交,其余走FallBack設(shè)置
之前提到OpenGL本身沒有PSO機(jī)制,但是UE這套PSO Cache的流程,也將OpenGL的渲染狀態(tài)抽象為PSO,起作用是PSO中的一部分信息BoundShaderState
UE雖然也提供了后臺(tái)異步編譯的功能,但是手游基本都會(huì)關(guān)閉此功能,而是在第一次加載游戲時(shí)全部一次性編譯完
Usage機(jī)制
默認(rèn)引擎會(huì)加載PSOList中的所有PSO,UsageMask可以添加篩選機(jī)制
LRU機(jī)制
生成的PSO可以緩存在內(nèi)存中,OpenGL和Vulkan提供了LRU機(jī)制,可以限制加載到內(nèi)存中的PSO數(shù)量,Metal沒有該機(jī)制
UE5+
多了一套PSO Precache流程 ?UE5.3首次出現(xiàn),5.4默認(rèn)開啟
https://dev.epicgames.com/documentation/en-us/unreal-engine/pso-precaching-for-unreal-engine?application_version=5.4
https://zhuanlan.zhihu.com/p/679832250
這是一套相對(duì)自動(dòng)收集PSO Cache的方案,在Loading后就開始走收集流程,并在后臺(tái)線程上異步編譯
目前僅適用于D3D12? 手游項(xiàng)目制作和Precache這套暫時(shí)無(wú)緣
如何控制項(xiàng)目材質(zhì)變體的數(shù)量
UE變體數(shù)太多會(huì)導(dǎo)致什么問題
如果變體數(shù)很多,影響游戲包體大小,首次運(yùn)行游戲時(shí)編譯PSOCache耗時(shí)會(huì)比較長(zhǎng),全量編譯PSO低端機(jī)可能會(huì)OOM,并且垃圾一點(diǎn)的手機(jī)編的也慢,加上發(fā)熱等,影響玩家第一次的游玩體驗(yàn)。Metal編譯生成的MemoryCache也會(huì)很大,而且隨著游戲版本持續(xù)運(yùn)營(yíng),又一直在出新效果玩法,對(duì)后續(xù)的膨脹問題就很難把控。還有圖形驅(qū)動(dòng)的升級(jí)會(huì)清掉PSO緩存,IOS升系統(tǒng)等導(dǎo)致得重新編譯一次,又影響體驗(yàn)。
是時(shí)候回憶一下UE的材質(zhì)系統(tǒng)了
VertexFactory
材質(zhì)面板中勾選Usage后,UE會(huì)編譯相應(yīng)VertexFactory的shader變體
https://zhuanlan.zhihu.com/p/707759496
FShader持有ShaderCode在FShaderMapResource中的索引
FShaderType
FShaderType是FShader的元類,負(fù)責(zé)橋接FShader與對(duì)應(yīng)的usf文件,FShader對(duì)應(yīng)的FShaderType用using指定
當(dāng)使用IMPLEMENT_MATERIAL_SHADER_TYPE時(shí),就會(huì)為FShader構(gòu)造一個(gè)相應(yīng)的FShaderType,將FShader、Shader入口函數(shù)名,ShaderFrequency橋接起來(lái),同時(shí)將FShaderType注冊(cè)到一個(gè)全局列表中。編譯Shader時(shí)會(huì)使用到這個(gè)全局列表
FMaterial/FMaterialResource
FMaterialShaderMap
FMaterialShaderMap中存儲(chǔ)著材質(zhì)在特定QualityLevel + ShaderPlatform下編譯出的所有shader數(shù)據(jù)
其父類FShaderMapBase中的幾個(gè)重要數(shù)據(jù)
FShaderMapResourceCode
FShaderMapResourceCode中存儲(chǔ)的是編譯后的shader代碼,通過FShader存儲(chǔ)的ShaderIndex索引
FShaderMapResource
FShaderMapResource負(fù)責(zé)創(chuàng)建和存儲(chǔ)多個(gè)RHI端的shader,其子類有FShaderMapResource_SharedCode和FShaderMapResource_InlineCode,對(duì)應(yīng)不同獲取ShaderCode的方式,SharedCode就是前文所說(shuō),如果項(xiàng)目設(shè)置勾選了ShareMaterialShaderCode,保存在.uasset中的代碼會(huì)統(tǒng)一放在.ushaderbytecode文件中,運(yùn)行時(shí)創(chuàng)建一個(gè)FShaderCodeLibrary管理
FShaderMapContent
FMaterialShaderMap持有一個(gè)FShaderMapContent的引用,FShaderMapContent存有特定VertexFactoryType和ShaderType設(shè)置下對(duì)應(yīng)的FShader實(shí)例
整體的流程可以分為倆個(gè)大的步驟,編譯流程和繪制流程
首先看編譯流程
https://zhuanlan.zhihu.com/p/85340922
https://zhuanlan.zhihu.com/p/707759496
材質(zhì)編輯器中連的藍(lán)圖節(jié)點(diǎn)可以理解為只是HLSL生成過程中的一種輸入,具體Pass用到什么shader,還得根據(jù)shader主干文件(如移動(dòng)端BasePass的MobileBasePassVertexShader.usf MobileBasePassPixelShader.usf),VertexFactory,Common文件等生成最終的HLSL,然后再根據(jù)對(duì)應(yīng)圖形API將HLSL編譯成對(duì)應(yīng)shaderCode
其中FHLSLMaterialTranslator ?MaterialTemplate.usf模版的填充,自定義材質(zhì)節(jié)點(diǎn)的一些使用之前也提過這里就不再提了
編譯流程,我們需要關(guān)心的大的步驟就是
UMaterial->FMaterial/FMaterialResource->FMaterialShaderMap
編譯好的ShaderCode是保存在FShaderMapResource中的
不同VertexFactoryType ?ShaderType對(duì)應(yīng)ShaderMap的生成邏輯在
FMaterialShaderMap::Compile()
FMaterial::GetDependentShaderAndVFTypes()中
https://zhuanlan.zhihu.com/p/467788335
然后是繪制流程
談及變體主要涉及的是MeshMaterialShader(MaterialShader的子類),那么就需要回憶下Mesh Draw Pipeline的流程
https://dev.epicgames.com/documentation/en-us/unreal-engine/mesh-drawing-pipeline?application_version=4.27
MeshBatch的Cache和Dynamic生成流程,后續(xù)的MeshPassProcessor和MeshDrawCommand生成流程之前講過,這里就不再重述了。
mesh如何知道自己對(duì)應(yīng)的vertexFactory就在生成MeshBatch流程中完成
FMeshBatchElement包含的是一個(gè)基本的繪制需要的信息
MeshDrawCommand包含了一次drawCall所需的全部信息,渲染信息的收集綁定是在MeshPassProcessor中完成的
渲染所需相關(guān)的數(shù)據(jù)由MeshPassProcessor收集
渲染時(shí)shader的獲取,關(guān)注
XXMeshProcessor::Process中的GetXXPassShaders如
其中根據(jù)RenderPass創(chuàng)建特定FShader對(duì)應(yīng)的FShaderType實(shí)例,最后用TryGetShaders方法獲取FShader實(shí)例
FMaterial::TryGetShaders中,先獲取FMaterial中的FShaderMapContent,然后用FShaderMapContent::GetShader通過ShaderType template實(shí)例字符串索引對(duì)應(yīng)的FShader實(shí)例
而FShader持有ShaderCode在FShaderMapResource中的索引
后續(xù)提交給RHI Thread找對(duì)應(yīng)的硬件編譯過的機(jī)器碼或者PSO Cache繪制即可
要更細(xì)的話,其實(shí)還有一個(gè)游戲加載時(shí)的流程
https://zhuanlan.zhihu.com/p/681306302
OK 在有了以上內(nèi)容的認(rèn)知之后,我們就可以來(lái)看一下UE項(xiàng)目中有哪些地方可以優(yōu)化變體和PSO Cache了
可以從正反倆角度出發(fā)分析
首先正向分析,項(xiàng)目中那些地方會(huì)影響產(chǎn)生的變體
https://zhuanlan.zhihu.com/p/681316533
1、靜態(tài)材質(zhì)開關(guān)
包含連連看中的staticSwitchParameter和.usf中項(xiàng)目自己加的#ifdef
設(shè)A為主材質(zhì)(無(wú)論有多少個(gè)靜態(tài)開關(guān)),BC為A的材質(zhì)實(shí)例,如果BC的開關(guān)override情況是相同的,那么BC會(huì)有倆個(gè)shaderMap,對(duì)應(yīng)的倆個(gè)shaderCode內(nèi)容是一樣的,經(jīng)過ShaderCodeLibrary相同結(jié)果剔除機(jī)制,進(jìn)包后是一個(gè)shaderCode。
這時(shí)D也是A的材質(zhì)實(shí)例,E是C的材質(zhì)實(shí)例,DE的開關(guān)override情況相同且與BC不同,那么DE也是倆個(gè)shaderMap,倆相同內(nèi)容的shaderCode,進(jìn)包后也是一個(gè)shaderCode。如果FE開關(guān)override沒改動(dòng),那么FEC是同一套shaderMap。
2、材質(zhì)Usage??注意這里的Usage和PSO Cache那個(gè)UsageMask不是一個(gè)概念
如前文所說(shuō),材質(zhì)Usage的設(shè)置主要影響VertexFactory組合
項(xiàng)目中的主材質(zhì),尤其是通用主材質(zhì),AutoUsage開關(guān)都應(yīng)該關(guān)閉,然后根據(jù)美術(shù)實(shí)際的使用情況,酌情考慮開關(guān)勾選以及是否需要拆分主材質(zhì)
3、PSO UsageMask
做更細(xì)致的UsageMask拆分
然后是反向的分析
項(xiàng)目打包流程的.shk .spc文件都是很好的參考用于分析項(xiàng)目實(shí)際用到的變體情況,當(dāng)然由于這倆是二進(jìn)制文件,所以還得轉(zhuǎn)成可閱讀的文本文件
正向分析看不到實(shí)際用到的ShaderType情況和項(xiàng)目中圖程側(cè)的一些管線上的自定義修改。從.shk .spc反向分析shaderType,VFType,QulityLevel等條目還是很有必要的