Direct3D的一些小貼士

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的調用,狀態塊可以將狀態的更改集中在一起,並且可以重用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章