Direct3D的一些小技巧收藏

rabbit729發表於2010-12-27

GPU效能除錯:

通常來說,使用CPU時間事件來除錯GPU是低效並且是不準確的。D3D API在多數命令下會阻塞,甚至是Draw函式。它會在一些時間片上做一些真正的工作,而這往往是不可預知的。因此,GPU的效能除錯只能用PIX或者是其他專用產品,例如NVIDIA’s NVPerfHUD來進行。

顯示卡所用的記憶體:

顯示卡所用的記憶體可以分為兩大類:本地的和非本地的(相對於顯示卡來說)。在顯示卡處理的某些資料型別的時候,需要本地記憶體,例如幀緩衝。 非本地記憶體,有時也成為AGP卡槽記憶體(AGP aperture),可以被顯示卡訪問的某些資料型別所在的系統記憶體,例如頂點緩衝。本地記憶體要比非本地記憶體快。

本地記憶體通常是在顯示卡內的,但是有些顯示卡可以共享系統記憶體,這通常是平衡速度和價格之間的選擇。在這種情況下,幀快取可以存在於系統記憶體中,而不是在本地記憶體中。這種技術下,顯示卡處理某些資料的速度比不使用共享記憶體的要慢,因為資料必須從I/O Bus(例如PCI-Express)上傳輸過來。但是這可以使顯示卡成本大大降低。在NVIDIA,這種技術被稱為TurboCache,而ATI稱之為HyperMemory。

著色器和著色模型:

Shader是執行在GPU上的,處理一些D3D流水管線上一些任務的程式。有三種型別的shader,他們分別對應三種可程式設計的stage:

Vertex shader (頂點著色器VS) stage, geometry shader (幾何著色器GS) stage, 還有pixel shader(畫素著色器PS) stage。其中幾何著色器只能在DX10平臺上使用。

著色模型(shader model)是在GPU上執行的虛擬機器。每個虛擬機器定義被稱為一種shader profile。並且包含了特定的組合語言。

著色器的職責:

著色器通常是流水管線中描述物體表面的部分。例如,一種看起來像木頭的材質被稱為木頭著色器(wood shader)。而在D3D中,這些著色語言指令集可以做的事情遠不止描述物體表面。他們可以用來計算光照,矩陣轉換,頂點動畫,進行裁切,動態生成新的幾何物體,等等。在Mental ray中,shader按照職責可以劃分為surface shader, light shader, shader shader, output shader等等。

在D3D中,這三種著色器的職責劃分並不是很明確。例如,光照計算過可以在頂點著色器,或者是畫素著色器中完成,這取決於應用程式的需求。因此,包含各種著色器的著色器集合應運而生。他們連結起來定義了一個工作流水線。

關於Direct3D 9 資源和記憶體型別:

D3D支援下列型別的資源:紋理(包括常規的和渲染目標render target),頂點緩衝,索引緩衝,字型,交換鏈(swap chain),狀態組,深度模板緩衝,特效等等。

有四種記憶體型別(池),資源可以在這裡分配:

·         預設Default:在顯示卡記憶體中,包括AGP卡槽記憶體和本地視訊記憶體。在裝置丟失之後,必須被釋放,重構。

·         託管Managed:存在於系統記憶體中,按需拷貝到視訊記憶體。

·         系統SystemMem:永遠存在於系統記憶體中,並且不能直接用於渲染。可以當作源或者目標拷貝。例如UpdateSurface和UpdateTexture。

·         Scrach: 永遠存在於系統記憶體中,並且不會被裝置大小或格式限制,例如紋理的2的冪限制。不能把它放到視訊記憶體中。

查詢資源洩露:

在關閉一個基於D3D的應用程式時,D3D除錯執行庫會報告記憶體洩露。按照以下步驟定位洩漏點。

1.       在DirectX Control Panel中(通常在DXSDK安裝目錄中可以找到),啟用“Use Debug Version of Direct3D 9”並且將Debug Output Level設定為”More”。確保Break on Memory Leaks被禁用。點選Apply。

2.       在VS中除錯執行應用程式。在關閉應用程式之後,檢視VS的輸出視窗Direct 3D9: (WARN) : Memory Address:  00xxxxxx,  IAllocID= xx dwSize = xxxxxxxx;(pid = xxxxx)

3.       每條記錄對應了一個資源洩漏,檢視並記住ID,然後在DirectX Control Panel中輸入ID並且點選Apply。

4.       再次執行程式,重複以上步驟。程式會在分配點中斷,你可以檢查哪裡遺忘釋放。

5.       當你除錯完成之後,別忘了將Break On AllocID設定為0。

處理裝置丟失(Device Lost)

一個D3D裝置可以在很多情況下丟失,例如從全屏向視窗轉換,一個電源管理事件,按CTRL+DEL+ALT返回Windows Security Dialog。

必須採取措施去檢查一個裝置是否丟失,丟失了之後如何恢復。

方法:在某些地方呼叫IDirect3DDevice9::TestCooperativeLevel,例如在每幀開始渲染之前呼叫。當發現裝置丟失之後,採取下列措施:

1.       釋放所有在Default記憶體中的資源

2.       釋放其他沒有和Default, Managed, SystemMem繫結的資源

3.       呼叫IDirect3DDevice9::TestCooperativeLevel去確認裝置是否可以被重置如果能,那麼呼叫IDirect3DDevice9::Reset 如果不能,繼續等待,然後再嘗試

4.       重新建立需要的資源

渲染目標和交換鏈(Render Targets and Swap Chains)

一個渲染目標是一個用於儲存在圖形流水線輸出畫素的表面。也就是說,它是一個顏色陣列。一個裝置可以有一個或者多個活動的渲染目標,可以通過SetRenderTarget來啟用。一個用於渲染目標的表面只能放在Default池中,有三種渲染目標:

·         渲染目標表面Render target surfaces(通過CreateRenderTarget建立)

·         渲染目標紋理Render target textures(tongguo D3DUSAGE_RENDERTARGET標識來建立)

·         交換鏈Swap chains 交換鏈就是後備緩衝的集合,它們能夠相繼渲染到前緩衝,也就是螢幕上。一個在交換鏈中的後備緩衝可以當作一個渲染目標賦給一個裝置。但是,不像其他的渲染目標,交換鏈可以渲染到螢幕上,因為交換鏈是和視窗/全屏大小繫結的。可以建立多個交換鏈,注意更改預設交換鏈大小會造成裝置丟失,所以視窗程式會忽略預設的交換鏈,而使用一個附加的交換鏈來避免這個問題。渲染目標可以被鎖定(用來讀取),但是當這個渲染目標是活動的話,會影響系統效能。我們可以根據需要用IDirect3DDevice9::GetRenderTargetData來將一個在Default池中的渲染目標拷貝出來。可以使用IDirect3DDevice9::StrechRectangle在兩個在顯示卡記憶體中的渲染目標中進行高效拷貝。

批處理(Batching)【重劍注:這個是重點】

D3D的效率在很大程度上受制於傳給API的幾何模型資料的批次上。一個批處理就是呼叫一次DrawPrimitive或者DrawIndexPrimitive。在GPU可以處理資料前,CPU花相當長時間來處理每批資料。現在常見的CPU和GPU,可以參考以下資料:

·         使用DX9,CPU每秒可以處理50,000批次;使用DX10,這個資料是200,000。

·         在DX9中,處理2,000個三角形在CPU和GPU所花的時間大致相等。在DX10中,這個資料是500。簡單的著色程式使這個數字增加,複雜的著色程式使這個數字減少。在CPU和GPU在同一個批次上花相同時間的情況下,例項化(Instancing)可以提高三角形的輸出能力。因為以上原因,每個批次中處理資料的數量越大越好,這樣能夠將三角形的吞吐量最大化。

在實踐中,具體有兩種方式:

·         Consolidation合併:將相同性質的幾何元素合併起來,通常是將一些屬性進行排序的結果

·         Instancing例項化:將相同的幾何物體,經過一些細微的,不同的變換後畫出多個例項來。例如世界座標系的轉換和顏色轉換。【重劍思考:Q:遊戲裡角色的護腕部位要同樣的模型,不能是一個護腕,一個手套,這個就是為了Instancing?A:非也!兩個護腕其實是一個模型,美術畫的時候就是畫了一對(左右各一個),中間就是斷開的】

頂點,索引緩衝Vertex / Index Buffer

頂點和索引緩衝有兩種型別:靜態和動態的。

一旦建立之後,靜態的緩衝使用起來比動態的快一倍。但是,動態緩衝的加鎖和解鎖要比靜態的快,它們是為更改的每一幀設計的,通常被儲存在AGP卡槽記憶體中。經常對靜態緩衝加解鎖是不明智的,因為只有等驅動完成了所有掛起的命令之後才能返回該緩衝的指標。如果經常這樣做,這會導致CPU和GPU很多不必要的同步,這樣效能將會變得很差。

為了得到最好的效能,必須採用動態快取。這樣驅動可以繼續進行並行渲染。使用DISCARD或者是NOOVERWRITING標誌可以實現這一點,這樣驅動可以在更新資料的同時繼續處理老的資料。

DISCARD:這個標誌說明應用程式不關心當前緩衝的內容。所以在緩衝被渲染的同時,驅動可以給應用程式一個全新的緩衝。這個處理稱之為“buffer renaming”。注意,在實踐中,驅動傾向於不去釋放“緩衝重新命名”中所用的記憶體,因此這個標誌必須儘量少用。

NOOVERWRITE:這表示,對於之前新增的,不帶這個標誌的資料,應用程式不會更改它。例如應用程式只會在現有緩衝之後新增資料。所以驅動可以繼續使用現有資料進行渲染。

CPU和GPU的並行處理

D3D runtime會將一堆命令做成命令串傳給GPU,這就允許GPU和CPU進行並行處理。這樣也是硬體加速渲染這麼高效的原因之一。但是,在很多情況下,CPU和GPU必須進行同步之後才能做進一步的處理。通常來說,應該儘量避免這種情況,因為這會導致整個流水管線的重新整理,大幅降低效能。例如,對靜態緩衝加鎖,這要求GPU先處理完所有的命令之後,才能返回被鎖緩衝的指標。如果用動態緩衝,就可以避免,就像前面講過的一樣。

有一些同步是不可避免的,例如,CPU可能會需要一些GPU還來不及處理的命令結果。在這種情況下,使用者會感到畫面延遲Lag。要避免這種情況,可以在GPU落後兩三幀的情況下呼叫Present來強迫CPU等待GPU。因此,呼叫Present可能比較慢,但是正式它處理了必要的同步。

狀態的更換State Changes

不管冗餘還是不冗餘,狀態的轉換在到達驅動層的時候,開銷總是很大。所以在某些層面,狀態轉換必須被過濾。一個對狀態進行更換的函式呼叫並不一定會開銷很大,因為D3D Runtime很有可能緩衝這些轉換請求,在真正呼叫DrawPrimitive函式之前不會去執行它。多次的狀態轉換也不會加大開銷,因為只使用最後一個狀態值。儘管如此,狀態轉換還是應該儘量避免。某些狀態轉換會比其他的轉換的開銷更大。例如,對於更改處於活動狀態的頂點緩衝和畫素緩衝會導致整個流水管線的重新整理。因為在某些顯示卡上,同一時間每個型別只有一個著色器可以處於活動狀態。一個圖形流水線可以很長,花一段時間才能完成一個畫素的渲染。因此,整個流水線的重新整理需要儘量避免。在不同的顯示卡上,某個狀態的更新的花費差別可能會很大。另外,D3D的函式呼叫個數也必須儘量的少,雖然它的開銷不如達到驅動層的狀態更改那麼大。可以使用狀態塊來減少D3D API的呼叫,狀態塊可以將狀態的更改集中在一起,並且可以重用。

編譯VS和PS:

 fxc /T vs_3_0 /E main(入口函式自己定義) /Fo test_vs.vso(編譯後輸出檔名) test_vs.txt(帶編譯vs名稱)

 

相關文章