国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

剖析虛幻渲染體系(12)- 移動端專題Part 3(渲染優(yōu)化)

defcon / 1427人閱讀

摘要:管線優(yōu)化管線優(yōu)化曲面細分期間消除子像素。然而,高級別的曲面細分可以產(chǎn)生子像素三角形,這導(dǎo)致光柵化利用率降低。另外,如果合并或批處理之后的物體包圍盒過大,反而會造成性能下降,因為無法有效使用遮擋剔除等技術(shù)進行剔除。

?

目錄
  • 12.6 移動端渲染優(yōu)化
    • 12.6.1 渲染管線優(yōu)化
      • 12.6.1.1 使用新特性
      • 12.6.1.2 管線優(yōu)化
      • 12.6.1.3 帶寬優(yōu)化
    • 12.6.2 資源優(yōu)化
      • 12.6.2.1 紋理優(yōu)化
      • 12.6.2.2 頂點優(yōu)化
      • 12.6.2.3 網(wǎng)格優(yōu)化
    • 12.6.3 Shader優(yōu)化
      • 12.6.3.1 語句優(yōu)化
      • 12.6.3.2 狀態(tài)優(yōu)化
      • 12.6.3.3 匯編級優(yōu)化
    • 12.6.4 綜合優(yōu)化
      • 12.6.4.1 光影優(yōu)化
      • 12.6.4.2 后處理優(yōu)化
      • 12.6.4.3 精靈渲染優(yōu)化
      • 12.6.4.4 均衡GPU工作負載
      • 12.6.4.5 Compute Shader優(yōu)化
      • 12.6.4.6 多核并行
      • 12.6.4.7 其它綜合優(yōu)化
    • 12.6.5 XR優(yōu)化
      • 12.6.5.1 注視點渲染(Foveated Rendering)
      • 12.6.5.2 多視圖(Multiview)
      • 12.6.5.3 立體渲染(Stereo Rendering)
      • 12.6.5.4 隱藏延時
      • 12.6.5.5 制定技術(shù)規(guī)格
      • 12.6.5.6 其它XR優(yōu)化
    • 12.6.6 調(diào)試工具
  • 12.7 本篇總結(jié)
    • 12.7.1 本篇思考
  • 團隊招員
  • 特別說明
  • 參考文獻

?

?

?

12.6 移動端渲染優(yōu)化

前面幾章詳盡地剖析了移動端GPU架構(gòu)的特性和機理,那么就可以指導(dǎo)我們抽象出一些準則,從而獲得高性能的渲染代碼和應(yīng)用程序。

為了獲得流暢、高效、良好體驗,每個應(yīng)用程序都必須重視性能優(yōu)化,并貫穿始終。應(yīng)用程序的性能優(yōu)化分為以下三角循環(huán):

第一步,分析應(yīng)用程序的整體性能。

第二步,利用工具定位出性能瓶頸。

第三步,修改應(yīng)用程序。回到第一步遞歸分析。

這個三角循環(huán)什么時候停止呢?那就是應(yīng)用程序的性能已經(jīng)達到了項目之初指定的標準(如高中低畫質(zhì)不低于多少幀,DC、三角面數(shù)小于多少等),并且已經(jīng)知道應(yīng)用程序已經(jīng)達到了效率極限,再往下便到了投入產(chǎn)出比很小的牛角尖。

本篇會涉及以下概念:

名稱 別名 描述
USC (Unified Shading Cluster) Shading Cluster, Shading Unit, Execution Unit 圖形核心的半自主部分,通常可以執(zhí)行整個工作組。其他大型部件如紋理單位(Texture Unit)可以在USC之間共享。
Core Processor, Graphics Core 圖形核心的一個幾乎完全自主的部分。通常情況下,是USC的集合以及可能支持的硬件,如紋理單元。
Task Thread Group, Warp, Wavefront USC執(zhí)行的線程的原生分組,PowerVR Rogue內(nèi)核由32個線程組成。
shared Shared variables 存儲于Shared memory的變量。
const / uniform const / uniform變量, uniform塊, uniform緩沖區(qū) 存儲于Constant memory的變量、塊、緩沖區(qū)。

補充一下PowerVR Rogue硬件架構(gòu)和數(shù)據(jù)流交互圖,如下所示:

PowerVR%20Rogue的Unified%20Shading%20Cluster(USC)如下所示:

另外,補充一下本章大量涉及的片元(fragment)的概念:

片元(fragment)是GPU內(nèi)部的幾何體光柵化后形成的最小表示單元,它經(jīng)過一系列片元操作(alpha測試,深度測試,模板測試等)后,才可能最終寫入渲染紋理成為像素(pixel)。所以,片元不是像素,但有概率成為像素。

不過在D3D或UE內(nèi)部,沒有片元的概念,像素包含了片元。

12.6.1%20渲染管線優(yōu)化 12.6.1.1%20使用新特性
  • Variable%20Rate%20Shading

Variable%20Rate%20Shading(VRS,可變率著色)允許像素著色器一次著色一個或多個像素,這樣一個著色計算可以代表一個像素或一組像素。VRS是反鋸齒技術(shù)的逆解。抗鋸齒技術(shù)通過平滑高變化的內(nèi)容,更頻繁地采樣每個像素,以避免走樣(aliasing)和鋸齒(jagged)邊緣。然而,如果要渲染的表面沒有高的顏色變化或?qū)⒃陔S后的通道上被模糊(例如,運動模糊),在每個像素都一個著色計算的操作通常是低效的。

VRS允許開發(fā)者指定著色率,其中只對一個像素執(zhí)行一個著色器計算,結(jié)果操作應(yīng)用于指定的像素組配置。如果使用得當(dāng),應(yīng)該不會導(dǎo)致視覺質(zhì)量下降,同時顯著減輕GPU渲染的負擔(dān),從而節(jié)省功耗并提高性能。

VRS示意圖。畫面根據(jù)顏色變化頻率采用不同的著色率,變化高的采用高著色率(如汽車),反之用低著色率(如左下和右下路面)。

VRS支持的常見著色率和運行機制。其中黃點是著色坐標,綠點是直接復(fù)用黃點的著色結(jié)果。

VRS在渲染管線的工作機制。VRS在光柵化階段采用指定著色率執(zhí)行光柵化,進入PS之后再放大。

UE可以給每個材質(zhì)設(shè)定1個著色率,在材質(zhì)屬性模板中:

VRS優(yōu)化的核心思想在于減少計算次數(shù)并復(fù)用周邊計算點的結(jié)果,從而達到提升渲染效率的目的。適合使用VRS的情形:

  • 顏色變化率低的物體。
  • 處于運動模糊區(qū)域的物體。
  • 景深范圍之外的物體。

使用移動端的VRS需要依賴不同圖形API的擴展:

// ------ OpenGLES ------// Qualcomm QCOM_shading_rateGL_SHADING_RATE_1X1_PIXELS_QCOMGL_SHADING_RATE_1X2_PIXELS_QCOM......// Arm / Imagination Tech(不支持)// ------ Vulkan ------VK_KHR_fragment_shading_rate
  • 使用Vulkan代替OpenGL。

相比OpenGL等傳統(tǒng)API,Vulkan支持多線程,輕量化驅(qū)動層,可以精確地管控GPU內(nèi)存、同步等資源,避免運行時校驗,基于命令隊列的機制,沒有全局狀態(tài)等等(下圖)。

得益于Vulkan的先進設(shè)計理念,使得它的渲染性能更高,通常在CPU、GPU、帶寬、能耗等指標都優(yōu)于OpenGL。但如果是應(yīng)用程序本身的CPU或者GPU負載高,則使用Vulkan的收益可能沒有那么明顯:

  • 使用遮擋剔除。

遮擋剔除可以提前剔除掉被遮擋的物體或者遠處占屏幕很小的物體,避免進入GPU管線,占用帶寬和計算資源。UE在移動端的遮擋剔除延遲了兩幀(因為BasePass結(jié)束之后才有深度緩沖,需要再增加一幀延遲確保結(jié)果可用):

然后是在RHI線程等待遮擋查詢的結(jié)果,遮擋查詢的結(jié)果是在渲染線程使用,由于延遲了兩幀,所以渲染線程在計算可見性時不需要等待:

使用遮擋剔除時,需要遵循以下建議:

1、只在需要時返回查詢結(jié)果,不要等待它,因為同步等待是非常低效的。

2、對于遮擋,只在必要時使用精確計數(shù)選項。OpenGL ES使用GL_ANY_SAMPLES_PASSED,、Vulkan使用VK_QUERY_CONTROL_PRECISE_BIT = false,除非確實需要知道遮擋的數(shù)量。

3、不要修改正在繪制調(diào)用中引用的資源。

4、不要將GL_MAP_INVALIDATE_BUFFER /GL_MAP_INVALIDATE_RANGE與glMapBufferRange()一起使用,因為這些標志在某些版本的驅(qū)動會觸發(fā)創(chuàng)建一個不必要的資源拷貝。

12.6.1.2 管線優(yōu)化

  • 曲面細分期間消除子像素。

曲面細分增加細節(jié)級別,并可以通過允許其他游戲子系統(tǒng)在低分辨率的網(wǎng)格表示上操作來減少內(nèi)存帶寬和CPU周期。然而,高級別的曲面細分可以產(chǎn)生子像素三角形,這導(dǎo)致光柵化利用率降低。利用距離、屏幕空間大小或其他自適應(yīng)度量來計算避免子像素三角形的曲面細分因子是很重要的。

  • 曲面細分期間開啟背面剔除。

圖元的背面剔除可以防止冗余的像素進入像素著色器中,從而提升性能。

  • 刪除未使用的render target或shader資源。

操作更多的RT或shader資源,會增加帶寬,降低性能。故而盡量刪除未引用的資源。

  • 避免GMEM加載。

在每個Pass渲染之前,需要調(diào)用圖形API明確清理RT。

OpenGL ES: glClear()

Vulkan: LOAD_OP_CLEAR / LOAD_OP_DONT_CARE

  • 使用subpass或PLS。

Vulkan的subpass(或OpenGL ES的PLS)可以讓多個pass的數(shù)據(jù)持續(xù)保存在GMEM(Tile緩沖區(qū))中,避免數(shù)據(jù)反復(fù)從GMEM和全局內(nèi)存之間傳輸,從而降低帶寬和延時。

  • 使用PSO緩存。運行時創(chuàng)建PSO對象比較消耗CPU性能,如果在離線階段收集、編譯材質(zhì)使用的Shader并保存成二進制文件,以便下次運行時調(diào)用時直接讀取Cache文件并轉(zhuǎn)成PSO對象,可以降低CPU負載。下圖是UE的PSO緩存機制圖示:

更多詳情請參看UE官方文檔:PSO%20Caching。

  • 使用僅深度(Z-only)渲染。

GPU有一種特殊的模式,可以以兩倍于正常模式的速率寫入Z-only像素,例如應(yīng)用程序渲染陰影圖。

有兩種方式可以讓GPU進入此模式:

1、圖形API明確指示,硬件才能進入這個特殊的渲染模式。

2、應(yīng)用程序通過特定的渲染狀態(tài)提示驅(qū)動程序。比如:使用一個空的片元著色器和禁用Frame%20Buffer(幀緩沖區(qū))寫掩碼。

一些渲染程序或引擎(如UE)會使用專用的PrePass來渲染深度,以充分利用Early-Z計算。不過對于移動端GPU需要謹慎對待,應(yīng)以實際測試為準。

  • 使用間接索引(indirect%20indexed)的繪制接口。

間接繪制調(diào)用將開銷從CPU轉(zhuǎn)移到GPU,從而減少CPU和GPU的帶寬。例如,在加載時緩存繪制調(diào)用參數(shù),以便在緩沖對象存儲中渲染網(wǎng)格。這些緩存數(shù)據(jù)可以作為glDrawArraysIndirect%20或glDrawElementsIndirect的輸入?yún)?shù)。

需要OpenGL%20ES%203.1才支持。

  • Draw%20Call優(yōu)化。
    • 合并幾何物體,同時合并它們的材質(zhì)。
    • 使用批處理,即便不是CPU受限,也可以減少能耗。
    • 使用實例化(instance)。
    • 使用非直接索引繪制。
    • 避免多次繪制小量物體。
    • 根據(jù)高中低畫質(zhì)設(shè)定合理的Draw%20Call數(shù)量。

使用批處理時,要注意頂點總數(shù)限制,不能超過索引的表達范圍(通常最大是65k)。另外,如果合并或批處理之后的物體包圍盒過大,反而會造成性能下降,因為無法有效使用Frustum%20Cullinig、遮擋剔除等技術(shù)進行剔除。

另外,需要注意提交的幾何物體具有相鄰性,盡量落在同一個Tile內(nèi),以減少覆蓋的Tile數(shù)量,降低帶寬,提升緩存命中率:

上:良好的幾何物體提交順序;下:錯誤的幾何物體提交順序。

  • 禁用Alpha Test / Discard。

Alpha Test會打亂TBR的正常流程,造成渲染管線Stall,在PowerVR尤為明顯(Alpha Test階段會寫回深度到HSR階段)。

因為TB(D)R在渲染不透明物體時普遍開啟了Early-Z技術(shù)和特殊的隱藏面消除技術(shù)(HSR、FPK),在此階段會開啟深度測試,并寫入通過了深度測試的片元深度。但是,如果開啟了Alpha Test或Shader中使用了Discard,無法在Early-Z/隱藏面消除技術(shù)階段就確定該片元的深度是否有效,必須等執(zhí)行完P(guān)S、Alpha Test等階段才行:

這樣就無法充分發(fā)揮HSR技術(shù)的優(yōu)勢,從而降低渲染性能。

可以使用Alpha Blend代替Alpha Test。如果確實需要Alpha Test,則物體的渲染順序需尊照此順序:Opaque -> Alpha-tested -> Blended。

  • 盡量減少Alpha Blend。

原因是延遲渲染器,比如PowerVR GPU,在片元著色器處理它之前計算片元的可見性,防止輸出圖像中的不可見片元被不必要地處理。如果需要透明對象,請盡量減少透明對象的數(shù)量。

由于Alpha Blend不能寫入深度,不能充分利用HSR/FPK,會引發(fā)Overdraw,提升帶寬和數(shù)據(jù)傳輸量。

如果確實需要,有以下優(yōu)化建議:

1、優(yōu)先使用unorm格式,而不是浮點數(shù)。(注意:此條來自Arm Mali的建議,其它GPU可能不一樣,以實測為主)

2、如果是不透明物體,應(yīng)禁用Blend和alpht to coverage。

3、不要在攜帶MSAA數(shù)據(jù)的浮點frame buffer上使用混合。

4、避免過高的OverDraw。監(jiān)控每像素基礎(chǔ)上生成的混合層數(shù)量,即使是簡單的著色器,混合層數(shù)量高會因為片元數(shù)量多而快速消耗時鐘周期。

5、考慮將大型UI元素分成不透明和透明部分。然后可以分別繪制不透明部分和透明部分,允許Early-ZS或FPK/HSR刪除不透明部分下面的OverDraw。

6、不要僅僅在片元著色器中將alpha設(shè)置為1.0來禁用混合。

  • 充分利用Early-Z和FPK/HSR剔除被遮擋的像素。

為了充分利用Early-Z,物體繪制順序應(yīng)該如下所示:

1、繪制不透明物體。從前向后繪制。

2、繪制鏤空(Masked)物體。從前向后繪制。

3、繪制半透明物體。從后向前繪制。

對于廣泛支持TBR架構(gòu)的移動端GPU,不建議開啟Prepass繪制專用的深度,否則反而會增加帶寬和Draw Call。

另外,在繪制不透明物體時,盡量做到以下幾點:

1、禁用discard語句。

2、禁用Alpha to Coverage。

3、禁止在片元著色器中修改深度。

若是違反以上任意一條,便會使Early-Z失效,強制使用Late-Z,從而降低渲染效率。

  • 充分開啟裁剪和測試。

裁剪技術(shù)包含遮擋剔除、視錐體裁剪、Scissor、距離裁剪、LOD等等。

測試包含背面測試、深度測試、模板測試等,但禁用透明度測試。

  • 禁用Z-Prepass。

移動端GPU基于TBR結(jié)構(gòu)通常內(nèi)置了像素級的剔除,無需再專門繪制一次深度。UE在移動端默認禁用了Z-Prepass。

  • 最小化模板緩沖的更新。

1、如果值相同,則使用KEEP而不是REPLACE。

2、有些渲染器(如UE)使用光照繪制Pass對(pair):第一個Pass用于創(chuàng)建模板緩沖,第二個Pass用于給未蒙版的片元著色。可以在第二個Pass重置模板值,以便為下一個光照配對做好準備,這樣可以避免多帶帶的模板清理操作。

UE的移動端場景渲染器在繪制光照時正是使用了此種模板清理優(yōu)化方式。

  • 正確調(diào)用圖形API。

    • 除非達到了目標性能,否則不要以導(dǎo)致GPU空閑的方式使用API。

    • 不要過早等待渲染管線中的圍欄(fence)和查詢(query)對象的查詢結(jié)果。

    • 調(diào)用glMapBufferRange()時使用GL_MAP_UNSYNCHRONIZED標記開啟異步,防止渲染管線卡頓。

    • 避免同步方式調(diào)用以下接口:

      • glFlush()。但是,某些GPU(如PowerVR)由于使用了雙緩沖機制,不會卡調(diào)用線程。
      • glFinish()
      • glReadPixels()
      • glWaitSync()
      • glClientWaitSync()
      • eglClientWaitSync()
      • 沒有GL_MAP_UNSYNCHRONIZED標記的glMapBufferRange()

      避免不必要地調(diào)用以上接口,調(diào)用次數(shù)越少越好。

    • 避免使用glFlush()來分割渲染通道,因為驅(qū)動程序(Mali)會在需要時自動刷新。

    • 盡可能執(zhí)行Clear。在繪制前或渲染通道開始時,使用glClear/glDiscardFramebufferEXT/glInvalidateFramebuffer執(zhí)行渲染紋理的清理,防止GPU讀取上一幀的數(shù)據(jù)到Tile緩沖區(qū)中,節(jié)省帶寬。Vulkan則使用loadOp。

    • 盡可能使用glColorMask屏蔽不需要寫入的顏色通道。

如果違反以上建議,有可能導(dǎo)致以下結(jié)果:

1、如果管道被耗盡,GPU在產(chǎn)生氣泡期間部分空閑,導(dǎo)致性能損失。

2、根據(jù)與系統(tǒng)動態(tài)電壓和頻率縮放電源管理邏輯的相互作用,可能會有一些性能不穩(wěn)定。

  • 優(yōu)化Command Buffer。

1、要獲得最佳性能,請設(shè)置ONE_TIME_SUBMIT_BIT標志。不要設(shè)置SIMULTANEOUS_USE_BIT,除非確實需要。

2、構(gòu)建每幀命令緩沖區(qū),而不是使用同步命令緩沖區(qū)。

3、如果替代方法是每次在應(yīng)用程序邏輯中重放相同的命令序列,則使用SIMULTANEOUS_USE_BIT。它比應(yīng)用程序手動重放命令更有效,但比一次性提交緩沖區(qū)更低效。

4、不要使用設(shè)置了RESET_COMMAND_BUFFER_BIT的命令池,會增加內(nèi)存管理開銷,因為驅(qū)動程序無法為池中的所有命令緩沖區(qū)使用單個大型分配器。

5、使用secondary command buffer來允許多線程渲染通道的構(gòu)造。

6、最小化每幀secondary command buffer的調(diào)用次數(shù)。

  • 優(yōu)化描述符集和布局(descriptor sets and layouts)。

1、盡可能多地打包描述符集綁定空間。

2、更新已經(jīng)分配但不再引用的描述符集,而不是重置描述符池和重新分配新的描述符集。

3、重用預(yù)分配的描述符集,避免更新相同的信息。

4、使用VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC或VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC綁定相同的UBO或SSBO,但不同的偏移量。 另一種選擇是構(gòu)建更多的描述符集。

5、不要在描述符集中留下空白,會浪費空間,阻斷訪問連續(xù)性。

6、不要留下未使用的條目(entry),因為復(fù)制和合并依舊有消耗。

7、不要在性能關(guān)鍵的代碼路徑上從描述符池(descriptor pool)分配描述符集。

8、如果不打算更改綁定偏移量,就不要使用DYNAMIC_OFFSET UBOs/SSBOs,因為處理動態(tài)偏移量會有很小的額外成本。低效的描述符集和布局未優(yōu)化的Vulkan描述符集和布局的負面影響可能會增加繪制調(diào)用的CPU消耗。

  • 避免渲染管線氣泡(空閑)。

以下幾種情況會產(chǎn)生渲染管線氣泡:

1、Command Buffer提交不夠頻繁。不經(jīng)常提交命令緩沖區(qū)會減少GPU處理隊列中的工作量,限制潛在的編排機會。

2、數(shù)據(jù)依賴。假設(shè)有渲染通道M和N,M在稍后的階段。當(dāng)N在管道中被M更早地使用時,數(shù)據(jù)依賴就產(chǎn)生了。數(shù)據(jù)依賴會導(dǎo)致延遲,在此期間必須做足夠的工作來隱藏結(jié)果生成中的延遲。

渲染管線氣泡示意圖。圖中顯示CPU、VS、PS都存在氣泡。

以下建議可以減少管線氣泡:

1、頻繁地提交Command Buffer。例如,為幀中的每個主要渲染通道之后,但渲染通道期間不宜提交。

2、如果某些情況導(dǎo)致了氣泡,嘗試填充氣泡技術(shù)。例如,通過在兩個渲染通道之間插入獨立的工作負載。

3、考慮在比使用依賴數(shù)據(jù)的階段更早的管道階段生成依賴數(shù)據(jù)。例如,計算(compute)階段適合為頂點著色階段生成輸入數(shù)據(jù)。而片元階段是不合適的,因為它的執(zhí)行晚于頂點著色階段管道,否則會造成卡頓和延時。

4、考慮在管道中的更后階段處理依賴數(shù)據(jù)。例如,片元著色使用來自其他片元著色的輸出比計算著色使用片元著色更好。

5、使用柵欄異步地將GPU的數(shù)據(jù)讀回CPU。千萬不用同步地調(diào)用從GPU讀取數(shù)據(jù)到CPU的接口,否則整個渲染管線將可能發(fā)生嚴重停滯。

此外,以下建議可以優(yōu)化渲染管線:

1、不要在管道的任何地方不必要地等待GPU數(shù)據(jù)。

2、不要等到幀結(jié)束才提交所有的渲染通道。

3、在沒有足夠的中間工作來隱藏延遲的情況下,不要在管道中創(chuàng)建任何逆向(backwards)的數(shù)據(jù)依賴。

4、不要使用vkQueueWaitIdle()或vkDeviceWaitIdle()。

  • 正確使用管線同步。

現(xiàn)代圖形API(如Vulkan)擁有非常細粒度的管線階段:

typedef enum VkPipelineStageFlagBits{    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT = 0x00000001,        // Vertex Stages    VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT = 0x00000002,    VK_PIPELINE_STAGE_VERTEX_INPUT_BIT = 0x00000004,    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT = 0x00000008,    VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT = 0x00000010,    VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT = 0x00000020,    VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT = 0x00000040,    // Fragment Stages    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT = 0x00000080,    VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT = 0x00000100,    VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT = 0x00000200,    VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT = 0x00000400,    // Compute Stages    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT = 0x00000800,    VK_PIPELINE_STAGE_TRANSFER_BIT = 0x00001000,    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT = 0x00002000,        VK_PIPELINE_STAGE_HOST_BIT = 0x00004000,    VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT = 0x00008000,    VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000,        (......)} VkPipelineStageFlagBits;

現(xiàn)代圖形API(如Vulkan)也包含了眾多同步對象:

1、Subpass依賴、Pipeline Barrier、Event等,用于單個Queue內(nèi)的精細粒度同步。

2、Semaphore(信號)用于跨Queue的較重度的依賴關(guān)系。

管線依賴存在兩個變量:srcStagedstStagesrcStage標明必須等待的管線階段(pipeline stage),dstStage標明在處理開始之前必須等待同步的管線階段。

為了更好的并行效率和更少的管線氣泡,srcStage越早越好,而dstStage越遲越好。如果srcStageVK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT時,將獲得最差的性能。

Semaphore可以使用pWaitDstStages指定具體的階段。

更具體地說,遵循以下準則,可以獲得更好的渲染效率:

1、srcStageMask被設(shè)置得越早越好。

2、dstStageMask被設(shè)置得越晚越好。

3、檢查依賴關(guān)系是向前的(比如srcStageMask是頂點或計算,dstStageMask是片元)還是向后的(如srcStageMask是片元,dstStageMask是頂點或計算)。 盡量減少使用向后依賴關(guān)系。

4、如果確實需要向后依賴,則在生成和消費資源之間添加足夠的延遲,以便隱藏向后依賴引起的調(diào)度氣泡。

5、使用srcStageMask = ALL_GRAPHICS_BIT 和 dstStageMask = FRAGMENT_SHADER_BIT 彼此同步兩個渲染通道。

6、零拷貝(Zero-copy)算法是最有效的,因此盡量減少TRANSFER拷貝操作的使用。密切關(guān)注TRANSFER副本對硬件流水線的影響。

7、只在需要時使用隊列內(nèi)屏障(intra-queue barrier),并在屏障之間盡可能多地安排工作。

8、不要讓硬件處于空閑狀態(tài)。

9、不要忘記重疊頂點/計算和片元之間的處理。

10、不要使用下面的srcStageMask到dstStageMask同步組合,因為它們會完全耗盡管道:

BOTTOM_OF_PIPE_BIT to TOP_OF_PIPE_BITALL_GRAPHICS_BIT to ALL_GRAPHICS_BITALL_COMMANDS_BIT to ALL_COMMANDS_BIT

11、如果合并管道屏障,請注意不要引入錯誤的依賴項。確保不打破頂點/片元重疊,并創(chuàng)建一個不必要的氣泡。

12、不要使用VkEvent信號并立即等待該事件,用vkCmdPipelineBarrier()。

13、不要在單個Queue中使用VkSemaphore進行依賴管理。

14、不要讓渲染管線留有太大的空閑(否則降低性能),也不要讓渲染管線留有太小的空閑(否則可能產(chǎn)生錯誤)。

  • 正確處理管線資源。

OpenGL ES為應(yīng)用開發(fā)人員提供了一個同步呈現(xiàn)模型,即使底層的執(zhí)行可能是異步的,必須反映數(shù)據(jù)資源在繪制調(diào)用時的狀態(tài)。如果一個應(yīng)用程序修改了一個資源,而一個掛起的draw調(diào)用仍在引用它,那么驅(qū)動程序必須采取規(guī)避操作來確保正確性。

驅(qū)動程序處理這些資源的同步行為時因GPU廠商而異,例如Mali驅(qū)動程序避免了阻塞和等待資源引用計數(shù)達到零,因為這樣做會耗盡管道并導(dǎo)致性能低下。Mali GPU會創(chuàng)建一個全新版本的資源,資源的舊版本或幽靈(Ghost)版本將一直保留,直到掛起的繪制調(diào)用完成,其引用計數(shù)降至零。其它一些驅(qū)動程序(如PowerVR)會卡住本幀的渲染管線,延遲到下一幀處理,引發(fā)性能下降。

這種行為開銷大,需要為新資源分配內(nèi)存,并在完成時清理空資源。如果更新不是完全替換,還需要從舊的資源緩沖區(qū)復(fù)制到新的資源緩沖區(qū)。

為了優(yōu)化資源,需要遵循以下建議:

1、避免修改已入隊的draw call引用的資源,可以使用N-buffered資源,并通過管道進行動態(tài)資源更新。

2、使用GL_MAP_UNSYNCHRONIZED標記,以允許使用glMapBufferRange()來補齊緩沖區(qū)中仍被動態(tài)繪制調(diào)用引用的未引用區(qū)域。不要將GL_MAP_INVALIDATE_BUFFER /GL_MAP_INVALIDATE_RANGE與glMapBufferRange()一起使用,因為這些標志在某些版本的驅(qū)動會觸發(fā)創(chuàng)建一個不必要的資源拷貝。

  • 高效地上傳紋理資源。

上傳紋理資源到到圖形硬件時,對于非壓縮紋理,按線性的掃描線上傳,對于壓縮的紋理,將會逐塊上傳。

部分GPU內(nèi)部(如PowerVR)使用獨特的布局來改善內(nèi)存訪問局部性和提高緩存效率。數(shù)據(jù)的重新格式化是由專用硬件在芯片上完成的,因此非常快。如果能遵循以下步驟更能提升性能:

1、在非性能關(guān)鍵時期上傳紋理,如初始化。有助于避免與紋理加載相關(guān)的幀率下降。

2、避免上傳幀期間(mid-frame)的紋理數(shù)據(jù)到已經(jīng)用于該幀的紋理對象。

3、在紋理上傳完成后執(zhí)行一個預(yù)熱(warm-up)步驟。依然是有助于避免與紋理加載相關(guān)的幀率下降。

前面提到的預(yù)熱(warm-up)步驟可以確保紋理立即完全上傳。默認情況下,glTexImage2D不會立即執(zhí)行上傳所需的所有處理,紋理是在第一次使用時完全上傳的。可以通過在屏幕上畫出一系列三角形或用有問題的紋理對象進行綁定處理來強制上傳。

12.6.1.3 帶寬優(yōu)化

  • 注意數(shù)據(jù)的存放位置。如:RAM、VRAM、Tile Buffer、GPU Cache,減少不必要的數(shù)據(jù)傳輸。
  • 關(guān)注數(shù)據(jù)的訪問類型。如:是只讀還是只寫操作,是否需要原子操作,是否需要緩存一致性。
  • 關(guān)注緩存數(shù)據(jù)的可行性,硬件可以緩存數(shù)據(jù)以供GPU后續(xù)操作快速訪問。可以通過以下幾點提升緩存命中率:
    • 提高傳輸速度,確保客戶端頂點數(shù)據(jù)緩沖區(qū)被用于盡可能少的繪制調(diào)用。理想情況下,應(yīng)用程序永遠不應(yīng)該使用它們。
    • 減少GPU在執(zhí)行調(diào)度或繪制調(diào)用時需要訪問的數(shù)據(jù)量。這樣可以讓盡量多的數(shù)據(jù)放到緩存行,提升命中率。
  • 使用紋理壓縮格式。優(yōu)先ASTC,其次是ETC、PVRTC、BC等壓縮格式。GPU的硬件通常都支持這類壓縮格式,可以快速地編解碼它們,并且可以一次性讀取更多的紋素內(nèi)容到GPU的緩存行,提升緩存命中率。
  • 使用位數(shù)更少的像素格式。如RGB565比RGB888少8位,ASTC_6X6代替ASTC_4x4等。Adreno支持的像素格式參見Spec Sheets。
  • 使用半精度(如FP16)取代高精度(FP32)數(shù)據(jù)。如模型頂點和索引數(shù)據(jù),并且可以使用SOA(Structure of Array)數(shù)據(jù)布局,而不用AOS。
  • 降分辨率渲染,后期再放大。可以減少帶寬、計算量,減少設(shè)備熱發(fā)熱量。
  • 盡量減少繪制次數(shù)。繪制數(shù)量的減少可以減少CPU和GPU之間、GPU內(nèi)部的帶寬和消耗。
  • 確保數(shù)據(jù)存儲在On-Chip內(nèi)。

利用PLS、Subpass的特性,可以實現(xiàn)移動端的延遲渲染、粒子軟混合等。下表是PowerVR GX6250在實現(xiàn)延遲渲染時,使用不同的位數(shù)和性能的關(guān)系:

配置 時間/幀(ms)
96bit + D32 20
128bit + D32 21
160bit + D32 23
192bit + D32 24
224bit + D32 28
256bit + D32 29
288bit + D32 39

以上可知,當(dāng)位數(shù)大于256,超過GX6250的最大位數(shù),數(shù)據(jù)無法完全存儲在On-Chip內(nèi),會外溢到全局內(nèi)存,導(dǎo)致每幀時間暴增10ms,增幅為34.5%。

因此,對每像素的數(shù)據(jù)進行精心的組裝、優(yōu)化和壓縮,保持數(shù)據(jù)能夠完全容納于On-Chip內(nèi),可有效提升性能,節(jié)省帶寬。

  • 避免多余的副本。

確保使用相同內(nèi)存的硬件組件(CPU、圖形核心、攝像機接口和視頻解碼器等)都訪問相同的數(shù)據(jù),而不需要進行任何中間復(fù)制。

  • 使用正確的標記創(chuàng)建Buffer、紋理等內(nèi)存。部分Mali GPU(如Bifrost)執(zhí)行以下幾個標記組合:

1、DEVICE_LOCAL_BIT | HOST_VISIBLE_BIT | HOST_COHERENT_BIT

2、DEVICE_LOCAL_BIT | HOST_VISIBLE_BIT | HOST_CACHED_BIT

3、DEVICE_LOCAL_BIT | HOST_VISIBLE_BIT | HOST_COHERENT_BIT | HOST_CACHED_BIT

4、DEVICE_LOCAL_BIT | LAZILY_ALLOCATED_BIT

其中HOST_VISIBLE_BIT | HOST_COHERENT_BIT | HOST_CACHED_BIT的內(nèi)存類型說明如下:

1、提供CPU上的緩存存儲,與內(nèi)存的GPU視圖一致,無需手動同步。

2、如果芯片組支持CPU與GPU之間的硬件一致性協(xié)議,則該GPU支持此標記組合。

3、由于硬件的一致性,它避免了手動同步操作的開銷。當(dāng)可用時,緩存的、一致的內(nèi)存優(yōu)先于緩存的、不一致的內(nèi)存類型。

4、必須用于CPU上的應(yīng)用軟件映射和讀取的資源。

5、硬件一致性的功耗很小,所以不能用于CPU上只寫的資源。對于只寫資源,通過使用Not Cached,一致內(nèi)存類型繞過CPU緩存。

關(guān)于LAZILY_ALLOCATED內(nèi)存類型說明:

1、是一種特殊的內(nèi)存類型,最初只支持GPU虛擬地址空間,而不是物理內(nèi)存頁面。如果訪問內(nèi)存,則根據(jù)需要分配物理頁。

2、必須與使用VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT創(chuàng)建的瞬態(tài)attachment一起使用。瞬態(tài)Image的目的是用作幀緩沖attachment,只存在于一個單一的渲染過程中,可以避免使用物理內(nèi)存。

3、不能將數(shù)據(jù)寫回全局內(nèi)存。

以下是Vulkan內(nèi)存標記的使用建議:

1、對于不可變資源,使用HOST_VISIBLE | HOST_COHERENT內(nèi)存。

2、對于CPU上只寫的資源,使用HOST_VISIBLE | HOST_COHERENT內(nèi)存。

3、使用memcpy()將更新寫入HOST_VISIBLE | HOST_COHERENT內(nèi)存,或者按順序?qū)懭胍垣@得CPU write-combine單元的最佳效率。

4、使用HOST_VISIBLE | HOST_COHERENT | HOST_CACHED內(nèi)存用于將資源讀回CPU,如果此組合不可以,則使用HOST_VISIBLE | HOST_CACHED。

5、使用LAZILY_ALLOCATED內(nèi)存用于僅在單個渲染過程中存在的臨時幀緩沖區(qū)附件。

6、只將LAZILY_ALLOCATED內(nèi)存用于TRANSIENT_ATTACHMENT幀緩沖區(qū)附件。

7、映射和取消映射緩沖區(qū)消耗CPU性能。因此要持久地映射經(jīng)常被訪問的緩沖區(qū),例如:統(tǒng)一緩沖區(qū)、數(shù)據(jù)緩沖區(qū)或動態(tài)頂點數(shù)據(jù)緩沖區(qū)。

  • 盡量使用零拷貝(Zero-Copy)路徑。

如下圖所示,通過使用EglImage實現(xiàn)Camera和OpenCL共享Original Image Data,OpenCL和OpenGL ES共享Final Image Data,從而達到零拷貝:

  • 將內(nèi)存訪問分組。

編譯器使用幾種啟發(fā)式方法,可以識別內(nèi)核中的內(nèi)存訪問模式,這些模式可以組合成讀或?qū)懖僮鞯耐话l(fā)傳輸。為了讓編譯器更好實現(xiàn)這種優(yōu)化,內(nèi)存訪問應(yīng)該盡可能緊密地組合在一起。

例如,將讀放在內(nèi)核的開頭,寫放在內(nèi)核的結(jié)尾,可以獲得最佳的效率。對更大的數(shù)據(jù)類型(如向量)的訪問也會盡可能地編譯為單個傳輸,加載1個float4比加載4個多帶帶的float值更好。

  • 合理使用Shared/Local內(nèi)存。

可以在Shader初期(如初始化),將常訪問的數(shù)據(jù)先讀取到Shared/Local內(nèi)存,提升訪問速度。

  • 以行優(yōu)先(Row-Major)的順序訪問內(nèi)存。

GPU通常會預(yù)讀取行相鄰的數(shù)據(jù)到GPU緩存中,如果著色器算法以行優(yōu)先的方式訪問,可以提升Cache命中率,降低帶寬。

  • GPU特定帶寬優(yōu)化。

Mali的Transaction%20elimination只有在以下情形適用:

1、采樣數(shù)據(jù)為1。

2、mimap級別為1。

3、image使用了COLOR_ATTACHMENT_BIT。

4、image沒有使用TRANSIENT_ATTACHMENT_BIT。

5、使用單一顏色附件。(Mali-G51%20GPU及之后沒有此限制)

6、有效的tile尺寸是16x16像素,像素數(shù)據(jù)存儲決定了有效的tile尺寸。

Mali%20GPU還支持AFBC紋理,可以減少顯存和帶寬。

12.6.2%20資源優(yōu)化 12.6.2.1%20紋理優(yōu)化
  • 使用壓縮格式。

ASTC由于出色的壓縮率,更接近原圖的畫質(zhì),適應(yīng)更多平臺而成為首選的紋理壓縮格式。因此,只要可能,盡量使用ASTC。除非部分古老的設(shè)備,無法支持ASTC,才考慮使用ETC、PVRTC等紋理壓縮格式。詳見12.4.14%20Adaptive%20Scalable%20Texture%20Compression。

  • 盡量使用Mipmaps。

紋理Mipmaps提供提升內(nèi)存占用來達到降低采樣紋理時的數(shù)據(jù)量,從而降低帶寬,提升緩沖命中率,同時還能提升畫質(zhì)效果。魚和熊掌皆可得,何樂而不為?具體地說表現(xiàn)在以下方面:

1、極大地提高紋理緩存效率來提高圖形渲染性能,特別是在強烈縮小的情況下,紋理數(shù)據(jù)更有可能裝在Tile%20Memory。

2、通過減少不使用mipmapping的紋理采樣不足而引起的走樣來提高圖像質(zhì)量。

但是,使用Mipmaps會提升33%的內(nèi)存占用。以下情況需要避免使用:

1、過濾不能被合理地應(yīng)用,例如對于包含非圖像數(shù)據(jù)的紋理(索引或深度紋理)。

2、永遠不會縮小的紋理,比如UI元素,其中texel總是一對一地映射到像素。

  • 使用打包的圖集。

打包圖集之后,有可能合批渲染或?qū)嵗秩荆瑴p少CPU和GPU的帶寬。

  • 尺寸保持2的N次方。

盡管目前的圖形API都已經(jīng)支持非2N的次方尺寸(NPOT)的紋理,但有充分的理由建議保持紋理尺寸在2的N次方(POT):

1、在大多數(shù)情況下,POT紋理應(yīng)該比NPOT紋理更受青睞,因為這為硬件和驅(qū)動程序的優(yōu)化工作提供了最好的機會。(例如紋理壓縮、Mimaps生成、緩存行對齊等)

2、2D應(yīng)用程序應(yīng)該不會因為使用NPOT紋理而出現(xiàn)性能損失(除非可能在上傳時)。2D應(yīng)用程序可以是瀏覽器或其他呈現(xiàn)UI元素的應(yīng)用程序,其中NPOT紋理以一對一的texel到pixel映射顯示。

3、保證長和寬都是32像素倍數(shù)的紋理,以便紋理上傳可以讓硬件優(yōu)化。

  • 最小化紋理尺寸。
  • 最小化紋理位深。
  • 最小化紋理組件數(shù)量。
  • 利用紋理通道打包多張貼圖。例如將材質(zhì)的粗糙度、高光度、金屬度、AO等貼圖打包到同一張紋理的RGBA通道上。
12.6.2.2%20頂點優(yōu)化
  • 使用分離位置的交錯的頂點布局。原因詳見12.4.11%20Index-Driven%20Vertex%20Shading。
  • 使用合適的頂點和索引存儲格式。降低數(shù)據(jù)精度可以降低內(nèi)存、帶寬,提高計算單元運算量。目前主流移動端GPU支持的頂點格式有:
GL_BYTEGL_UNSIGNED_BYTEGL_SHORTGL_UNSIGNED_SHORTGL_FIXEDGL_FLOATGL_HALF_FLOATGL_INT_2_10_10_10_REVGL_UNSIGNED_INT_2_10_10_10_REV
  • 考慮幾何物體實例化。現(xiàn)代移動端GPU普遍支持實例化渲染,通過提交少量的幾何數(shù)據(jù)可以繪制多次,來降低帶寬。每個實例允許擁有自己的數(shù)據(jù),如顏色、變換矩陣、光照等。常用于樹、草、建筑物、群兵等物體。

  • 圖元類型使用三角形。現(xiàn)代GPU設(shè)計便是處理三角形,如果是四邊形之類的很可能會降低效率。

  • 減少索引數(shù)組大小。如使用條帶(strip)格式代替簡單列表格式,使用原始的有效索引代替退化三角形。

  • 對于轉(zhuǎn)換后緩存(%20post-transform%20cache),局部地優(yōu)化索引。

  • 避免使用低空間一致性的索引緩沖區(qū)。會降低緩存命中率。

  • 使用實例屬性來解決任何統(tǒng)一的緩沖區(qū)大小限制。%20例如,16KB的統(tǒng)一緩沖區(qū)。

  • 每個實例使用2的N次方個頂點。

  • 優(yōu)先使用gl_InstanceID到統(tǒng)一緩沖區(qū)或著色器存儲緩沖區(qū)的索引查找,而不是逐實例屬性數(shù)據(jù)。

12.6.2.3%20網(wǎng)格優(yōu)化
  • 使用LOD。

使用網(wǎng)格的LOD可以提升渲染性能和降低帶寬。相反,不使用LOD,會造成性能瓶頸。

同個網(wǎng)格不同LOD的線框模式。

以下是浪費計算和內(nèi)存資源的例子:

1、使用大量多邊形的對象不會覆蓋屏幕上的一個小區(qū)域,比如一個遙遠的背景對象。

2、使用多邊形的細節(jié),將永遠不會看到由于相機的角度或裁剪(如物體在視野錐之外)。

3、為對象使用大量的圖元。實際上可以用更少的圖元來繪制,還能保證視覺效果不損失。

  • 簡化模型,合并頂點。通過合并相鄰很近的頂點,可以有效減少網(wǎng)格頂點數(shù)量,利用網(wǎng)格簡化技術(shù),可以生成良好的LOD數(shù)據(jù)。

  • 離線合并靠在一起的小網(wǎng)格。如沙石、植被等。

  • 單個網(wǎng)格的頂點數(shù)不能超過65k。主要是移動端的頂點索引精度是16位,最大值是65535。

  • 刪除看不見的圖元。例如箱子內(nèi)部的三角形。

  • 使用簡單的幾何物體,配合法線貼圖、凹凸貼圖增加細節(jié)。

  • 避免小面積的三角形。

Quad的繪制機制,會導(dǎo)致小面積的三角形極大提升OverDraw。在PowerVR硬件上,對于覆蓋低于32個像素的三角形,會影響光柵化的效率,導(dǎo)致性能瓶頸。

提交許多小三角形可能會導(dǎo)致硬件在頂點階段花費大量時間處理它們,此階段主要影響因素是三角形的數(shù)量而不是大小。尤其會導(dǎo)致平鋪加速器( tile accelerator,TA)固定功能硬件的瓶頸。數(shù)量眾多的小三角形將導(dǎo)致對位于系統(tǒng)內(nèi)存中的參數(shù)緩沖區(qū)(parameter buffer)的訪問次數(shù)增加,增加內(nèi)存帶寬占用。

  • 保證網(wǎng)格內(nèi)每個圖元至少能創(chuàng)建10~20個像素。
  • 使用幾乎等邊的三角形。可以使面積與邊長的比例最大化,減少生成的片元Quad的數(shù)量。
  • 避免細長的三角形。

和小三角形類似,細長三角形(下圖紅色所示)也會產(chǎn)生更多無效的像素,占用更高的GPU資源,提高Overdraw。

  • 避免使用扇形或類似的幾何布局。三角形扇形的中心點具有較高的三角形密度,以致每個三角形具有非常低的像素覆蓋率。可以考慮Tile軸對齊的切割,但會引入更多三角形。(下圖)

扇形(圖左)進行Tile軸對齊的切割后產(chǎn)生的三角形數(shù)量(圖右)。

12.6.3 Shader優(yōu)化

12.6.3.1 語句優(yōu)化

  • 使用適當(dāng)?shù)臄?shù)據(jù)類型。

在代碼中使用最合適的數(shù)據(jù)類型可以使編譯器和驅(qū)動程序優(yōu)化代碼,包括shader指令的配對。使用vec4數(shù)據(jù)類型而不是float可能會阻止編譯器執(zhí)行優(yōu)化。

int4 ResultOfA(int4 a) {    return a + 1; // int4和int相加, 只需要1條指令.}int4 ResultOfA(int4 a) {    return a + 1.0; // int4和float相加, 需要3條指令: int4 -> float4 -> 相加 -> int4}
  • 減少類型轉(zhuǎn)換。
uniform sampler2D ColorTexture;in vec2 TexC;vec3 light(in vec3 amb, in vec3 diff){    // 紋理采樣返回vec4, 會隱性轉(zhuǎn)換成vec3, 多出1條指令.    vec3 Color = texture(ColorTexture, TexC);     Color *= diff + amb;    return Color;}// 以下代碼中, 輸入?yún)?shù)/臨時變量/返回值都是vec4, 沒有隱性類型轉(zhuǎn)換, 比上面代碼少1條指令.uniform sampler2D ColorTexture;in vec2 TexC;vec4 light(in vec4 amb, in vec4 diff){    vec4 Color = texture(Color, TexC);    Color *= diff + amb;    return Color;}
  • 打包標量常數(shù)。

將標量常數(shù)填充到由四個通道組成的向量中,大大提高了硬件獲取效率。在GPU骨骼動畫系統(tǒng)中,可增加蒙皮的骨骼數(shù)量。

float scale, bias;  // 兩個float值.vec4 a = Pos * scale + bias; // 需要兩條指令.vec2 scaleNbias; // 將兩個float值打包成一個vec2vec4 a = Pos * scaleNbias.x + scaleNbias.y; // 一條指令(mad)完成.
  • 使用標量操作。

要小心標量操作向量化,因為相同的向量化輸出需要更多的時間周期。例如:

highp vec4 v1, v2;highp float x, y;// Bad!!v2 = (v1 * x) * y; // vector*scalar接著vector*scalar總共8個標量muladd.// Good!!v2 = v1 * (x * y); // scalar*scalar接著vector*scalar總共5個標量muladd.

12.6.3.2 狀態(tài)優(yōu)化

  • 盡量使用const。

如果正確使用,const關(guān)鍵字可以提供顯著的性能提升。例如,在main()塊之外聲明一個const數(shù)組的著色器比沒有的性能要好得多。

另一個例子是使用const值引用數(shù)組成員。如果值是const,GPU可以提前知道數(shù)字不會改變,并且數(shù)據(jù)可以在運行著色器之前被預(yù)讀取,從而降低Stall。

  • 保持著色器指令數(shù)量合理

過長的著色器通常比較低效,比如需要在一個著色器中包含相對于紋理獲取數(shù)量的許多指令槽,可以考慮將算法分成幾個部分。

由算法的一部分生成的值可以存儲到紋理中,然后通過采樣紋理來獲取。然而,這種方法在內(nèi)存帶寬方面代價昂貴。以下情形也會降低紋理采樣效率:

1、使用三線性、各向異性過濾、寬紋理格式、3D和立方體貼圖紋理、紋理投影;

2、使用不同Lod梯度的紋理查找;

3、跨像素Quad的梯度計算。

  • 最小化shader指令數(shù)。

現(xiàn)代shader編譯器通常會執(zhí)行特定的指令優(yōu)化,但它不是自動有效的。很多時候需要人工介入,分析著色器,盡可能減少指令,即使是節(jié)省一條指令也值得。

  • 避免使用全能著色器(uber-shader)。

uber-shader使用靜態(tài)分支組合多個著色器到一個單一的著色器。如果試圖減少狀態(tài)更改和批處理繪制調(diào)用,那么是有意義的。然而,通常會增加GPR數(shù)量,從而影響性能。

  • 高效地采樣紋理。

紋理采樣(過濾)的方式很多,性能和效果通常成反比:

紋理的部分過濾類型及對應(yīng)效果圖。

要做到高效地采樣紋理,必須遵循以下規(guī)則:

1、避免隨機訪問,保持采樣在同一個2x2像素Quad內(nèi),命中率高,著色器更有效率。

2、避免使用3D紋理。由于需要執(zhí)行復(fù)雜的過濾來計算結(jié)果值,從體積紋理中獲取數(shù)據(jù)通常比較昂貴。

3、限制Shader紋理采樣數(shù)量。在一個著色器中使用四個采樣器是可以接受的,但采樣更多的紋理可能會導(dǎo)致性能瓶頸。

4、壓縮所有紋理。這允許更好的內(nèi)存使用,轉(zhuǎn)化為渲染管道中更少的紋理停頓。

5、考慮開啟Mipmaps。Mipmaps有助于合并紋理獲取,并有助于以增加內(nèi)存占用為代價的提高性能。同時還能降低帶寬,提升緩存命中率。

6、盡量使用簡單的紋理過濾。性能從高到低(效果從低到高)的采樣方式:最近點(nearest)、雙線性(bilinear)、立方(cubic)、三線性(tri-linear)、各向異性(anisotropic)。越復(fù)雜的采樣方式,會讀取越多的數(shù)據(jù),從而提升內(nèi)存訪問帶寬,降低緩存命中率,造成更大的延遲。需要格外注意這一點。

7、優(yōu)先使用texelFetch / texture(),通常會比紋理采樣效率更高(但需要工具分析驗證)。

8、謹慎對待預(yù)計算紋理LUT。實時渲染中,很常將復(fù)雜計算的結(jié)果編碼到紋理中,并將其用作查找表(如IBL的輻照度圖,皮膚次表面散射預(yù)積分圖)。這種方式只會在著色器是瓶頸時提升性能。如果函數(shù)參數(shù)和查找表中的紋理坐標在相鄰片元之間相差很大,那么緩存效率就會受到影響。應(yīng)該執(zhí)行性能概要分析,以確定此法是否有實際上的提升。

9、使用mediump sampler代替highp sampler,后者的速度是前者的一半。

10、各向異性過濾(Anisotropic Filtering,AF)優(yōu)化建議:

(1)先使用2x各向異性,評估它是否滿足質(zhì)量要求。較高的樣本數(shù)量可以提高質(zhì)量,但也會帶來效益遞減,并且往往與性能成本不相稱。

(2)考慮使用2x雙線性各向異性,而非三線性各向同性。在各向異性高的區(qū)域,2x雙線性算法速度更快,圖像質(zhì)量更好。注意,通過切換到雙線性過濾,可以在mipmap級別之間的過度點上看到接縫。

(3)只對受益最大的對象使用各向異性和三線性濾波。注意,8x三線性各向異性的消耗是簡單雙線性過濾的16倍!

  • 盡量避免依賴紋理讀取(Dependent texture read)。

依賴紋理讀取是一種特殊的紋理讀取,其中紋理坐標依賴于著色器中的一些計算(而不是某種規(guī)律變化)。由于這個計算的值不能提前知道,它不可能預(yù)取紋理數(shù)據(jù),因此在著色器處理降低緩存命中率,引發(fā)卡頓。

頂點著色紋理查找總是被視作依賴紋理讀取,就像片元著色中基于zw通道變化的紋理讀取。在一些驅(qū)動程序和平臺版本中,如果給定帶有無效w的Vec3或Vec4,則Texture2DProj()也可以作為依賴紋理讀取。

與依賴紋理讀取相關(guān)的成本在某種程度上可以通過硬件線程調(diào)度來抵消,特別是著色器涉及大量的數(shù)學(xué)計算。這個過程涉及到線程調(diào)度程序暫停當(dāng)前線程并在另一個線程中交換到USC上的處理。這個交換的線程將盡可能多地處理,一旦紋理獲取完成,原始線程將被交換回(下圖)。

GPU的Context需要訪問緩存或內(nèi)存,會導(dǎo)致若干個時鐘周期的延遲,此時調(diào)度器會激活第二組Context以利用ALU。

GPU越多Context可用就越可以提升運算單元的吞吐量,上圖的18組Context的架構(gòu)可以最大化地提升吞吐量。

雖然硬件會盡力隱藏內(nèi)存延遲,但為了獲得良好的性能,應(yīng)該盡可能避免依賴紋理讀取。應(yīng)用程序盡量在片元著色器執(zhí)行之前就計算出紋理坐標。

  • 避免使用動態(tài)分支

動態(tài)分支會延遲shader指令時間,但如果分支的條件是常量,則編譯器就會在編譯器進行優(yōu)化。否則如果條件語句和uniform、可變變量相關(guān),則無法優(yōu)化。其它建議:

1、最小化空間相鄰著色線程中的動態(tài)分支。

2、使用min(), max(), clamp(), mix(), saturate()等內(nèi)置函數(shù)避免分支語句。

3、檢查分支相對于計算的好處。例如,跳過距離相機閾值以上的像素進行光照計算,通常比直接進行計算會更快。

  • 打包shader插值數(shù)據(jù)。

著色器插值需要GPR(General Purpose Register,通用寄存器)傳遞數(shù)據(jù)到像素著色器。GPR的數(shù)量有限,若占滿,會導(dǎo)致Stall,所以盡量減少它們的使用。

能使用uniform的就不用varying。將值打包在一起,因為所有varying都有四個組件,不管它們是否被使用,比如將兩個vec2紋理坐標放入一個vec4。也存在其它更有創(chuàng)意的打包和實時數(shù)據(jù)壓縮。

  • 減少著色器GRP的占用。

占用越多的GPR(General Purpose Register,通用寄存器)意味著計算量大,如果沒有足夠的可用寄存器時,可能會導(dǎo)致寄存器溢出,從而導(dǎo)致性能欠佳。以下一些措施可以減少GRP的占用:

1、使用更簡單的著色器。

2、修改GLSL以減少哪怕是一條指令,有時也能減少一個GPR的占用。

3、不展開循環(huán)(unrolling loop)也可以節(jié)省GPRs,但取決于著色器編譯器。

4、根據(jù)目標平臺配置著色器,確保最終選擇的解決方案是最高效的。

5、展開循環(huán)傾向于將紋理獲取放到著色器頂部,導(dǎo)致需要更多的GPR來保存多個紋理坐標并同時獲取結(jié)果。

6、最小化全局變量和局部變量的數(shù)量。減少局部變量的作用域。

7、最小化數(shù)據(jù)維度。比如能用2維的就不要用3維。

8、使用精度更小的數(shù)據(jù)類型。如FP16代替FP32。

  • 在著色器上避免常量的數(shù)學(xué)運算

自從著色器出現(xiàn)以來,幾乎每一款發(fā)行的游戲都在著色器常量上花費了不必要的數(shù)學(xué)運算指令。需要在著色器中識別這些指令,將這些計算移到CPU上。在編譯后的代碼中識別著色器常量的數(shù)學(xué)運算可能更容易。

  • 避免在像素著色器中使用discard等語句。

一些開發(fā)者認為,在像素著色器中手動丟棄(也稱為殺死)像素可以提高性能。實際上沒有那么簡單,有以下原因:

1、如果線程中的一些像素被殺死,而同Quad的其他像素沒有,著色器仍然執(zhí)行。

2、依賴于編譯器如何生成微代碼(Microcode)。

3、某些硬件架構(gòu)(如PowerVR)會禁用TBDR的優(yōu)化,造成渲染管線的Stall和數(shù)據(jù)回寫。

  • 避免在像素著色器中修改深度

理由類同上一條。

  • 避免在VS里采樣紋理。

雖然目前主流的GPU已經(jīng)使用了統(tǒng)一著色器架構(gòu),VS和PS的執(zhí)行性能相似。但是,還是得確保在VS對紋理操作是局部的,并且紋理使用壓縮格式。

  • 拆分特殊的繪制調(diào)用

如果一個著色器瓶頸在于GPR和/或紋理緩存,拆分Draw Call到多個Pass反而可以增加性能。但結(jié)果難以預(yù)測,應(yīng)以實際性能測試為準。

  • 盡量使用低精度浮點數(shù)

FP16的運算性能通常是FP32的兩倍,所以shader中盡可能使用低精度浮點數(shù)。

precision mediump float;#ifdef GL_FRAGMENT_PRECISION_HIGH    #define NEED_HIGHP highp#else    #define NEED_HIGHP mediump#endif        varying vec2 vSmallTexCoord;varying NEED_HIGHP vec2 vLargeTexCoord;

UE也對浮點數(shù)做了封裝,以便在不同平臺和畫質(zhì)下自如低切換浮點數(shù)的精度。

  • 盡量將PS運算遷移到VS。

通常情況下,頂點數(shù)量明顯小于像素數(shù)量。通過將計算從像素著色器遷移到頂點著色器,可以減少GPU工作負載,有助于消除冗余計算。

例如,拆分光照計算的漫反射和高光反射,將漫反射遷移到VS,而高光反射保留在PS中,這樣能獲得效果和效率良好平衡的光照結(jié)果。

  • 優(yōu)化Uniform / Uniform Buffer。

1、保持Uniform數(shù)據(jù)盡可能地小。不超過128字節(jié),以便在多數(shù)GPU良好地運行任意給定的著色器。

2、將Uniform改成OpenGL ES的帶有#define的編譯時常量,Vulkan的專用常量,或者著色源中的靜態(tài)語法。

3、避免Uniform的向量或矩陣中存在常量,例如總是0或1的元素。

4、優(yōu)先使用glUniform()設(shè)置uniform,而不是從buffer中加載。

5、不要動態(tài)地索引uniform數(shù)組。

6、不要過度使用實例化。使用gl_InstanceID訪問Instanced uniform就是動態(tài)索引,無法使用寄存器映射的Uniform。

7、將Uniform的相關(guān)計算盡可能地移到CPU的應(yīng)用層。

8、盡量使用uniform buffer代替著色器存儲緩沖區(qū)(shader storage buffer)。只要uniform buffer空間充足,就盡量使用之。如果uniform buffer對象在GLSL中靜態(tài)索引,并且足夠小,驅(qū)動程序或編譯器可以將它們映射到用于默認統(tǒng)一塊全局變量的相同硬件常量RAM中。

  • 保持UBO占用盡可能地小。

如果UBO小于8k,則可以放進常量存儲器,將獲得更高的性能。否則,會存儲在全局內(nèi)存,存取時間周期顯著增加。

  • 選擇更優(yōu)的著色算法。

選擇更優(yōu)的有效的算法比低級別(指令級)的優(yōu)化更重要。因為前者更能顯著地提升性能。

  • 選擇合適的坐標空間。

頂點著色器的一個常見錯誤是在模型空間、世界空間、視圖空間和剪輯空間之間執(zhí)行不必要的轉(zhuǎn)換。如果模型世界轉(zhuǎn)換是剛體轉(zhuǎn)換(只包含旋轉(zhuǎn)、平移、鏡像、光照或類似),那么可以直接在模型空間中進行計算。

避免將每個頂點的位置轉(zhuǎn)換為世界或視圖空間,更好的做法是將uniforms(如光的位置和方向)轉(zhuǎn)換到模型空間,因為它是一個逐網(wǎng)格的操作,計算量更少。在必須使用特定空間的情況下(例如立方體映射反射),最好整個Shader都使用這個空間,避免在同一個shader中使用多個坐標空間。

  • 優(yōu)化插值(Varying)變量。

減少插值變量數(shù)量,減少插值變量的維度,刪除無用(片元著色器未使用)的插值變量,緊湊地打包它們,盡可能使用中低精度數(shù)據(jù)類型。

  • 優(yōu)化原子(Atomic)。

原子操作在許多計算算法和一些片元算法中比較常見。通過一些微小的修改,原子操作允許許多算法在高度并行的GPU上實現(xiàn),否則將是串行的。

原子的關(guān)鍵性能問題是爭用(contention)。原子操作來自不同的著色器核心。要達到相同的高速緩存行(cache line),需要數(shù)據(jù)一致性訪問L2高速緩存。

通過將原子操作保持在單個著色器核心來避免爭用,當(dāng)著色器核心在L1中控制必要的緩存行時,原子是最高效的。以下是具體的優(yōu)化建議:

1、考慮在算法設(shè)計中使用原子時如何避免爭用。

2、考慮將原子間距設(shè)置為64個字節(jié),以避免多個原子在同一高速緩存行上競爭。

3、考慮是否可以通過累積到共享內(nèi)存原子中來分攤爭用。然后,讓其中的一個線程在工作組的末尾推送全局原子操作。

  • 充分利用指令緩存(Instruction cache)。

著色器核心指令緩存是一個經(jīng)常被忽略的影響性能的因素。由于并發(fā)運行的線程數(shù)量眾多,因此足夠重視指令緩存對性能的重要性。優(yōu)化建議如下:

1、使用較短的著色器與更多的線程,而不是更長的色器與少量的線程。較短的著色器指令在緩存中更有可能被命中。

2、使用沒有動態(tài)分支的著色器。動態(tài)分支會減少時間局部性,增加緩存壓力。

3、不要過于激進地展開循環(huán)(unroll loop),盡管一些展開可能有所幫助。

4、不要從相同的源代碼生成重復(fù)的著色程序或二進制文件。

5、小心同個tile內(nèi)存在多個可見的片元著色(即Overdraw)。所有未被Early-ZS或FPK/HSR剔除的片元著色器

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/123981.html

相關(guān)文章

  • [面試專題]一線互聯(lián)網(wǎng)大廠面試總結(jié)

    摘要:道阻且長啊前端面試總結(jié)前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進按鈕書簽?zāi)夸洖g覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構(gòu)建的,使用自主研發(fā)的渲染引擎,和都使用網(wǎng)絡(luò)用來 道阻且長啊TAT(前端面試總結(jié)) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...

    lemanli 評論0 收藏0
  • [面試專題]一線互聯(lián)網(wǎng)大廠面試總結(jié)

    摘要:道阻且長啊前端面試總結(jié)前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進按鈕書簽?zāi)夸洖g覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構(gòu)建的,使用自主研發(fā)的渲染引擎,和都使用網(wǎng)絡(luò)用來 道阻且長啊TAT(前端面試總結(jié)) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...

    xfee 評論0 收藏0
  • [面試專題]一線互聯(lián)網(wǎng)大廠面試總結(jié)

    摘要:道阻且長啊前端面試總結(jié)前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進按鈕書簽?zāi)夸洖g覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構(gòu)建的,使用自主研發(fā)的渲染引擎,和都使用網(wǎng)絡(luò)用來 道阻且長啊TAT(前端面試總結(jié)) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...

    leap_frog 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<