日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Effulgent的《深入理解Direct3D9》整理版(转)

發布時間:2023/12/9 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Effulgent的《深入理解Direct3D9》整理版(转) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

深入理解Direct3D9

深入理解D3D9對圖形程序員來說意義重大,我把以前的一些學習筆記都匯總起來,希望對朋友們有些所幫助,因為是零散筆記,思路很雜,還請包涵。

其實只要你能完美理解D3DLOCK、D3DUSAGE、D3DPOOL、LOST DEVICE、QUERY、Present()、BeginScene()、EndScene()等概念,就算是理解D3D9了, 不知道大家有沒有同感。有如下幾個問題,如果你能圓滿回答就算過關:)。

?

問題1、 D3DPOOL_DEFAULT、D3DPOOL_MANAGED、D3DPOOL_SYSTEMMEM和D3DPOOL_SCRATCH到底有何本質區別?
2、 D3DUSAGE的具體怎么使用?
3、 什么是Adapter?什么是D3D Device?HAL Device和Ref Device有何區別?Device的類型又和Vertex Processing類型有什么關系?
4、 APP(CPU)、RUNTIME、DRIVER、GPU是如何協同工作的?D3D API是同步函數還是異步函數?
5、 Lost Device到底發生了什么?為什么在設備丟失后D3DPOOL_DEFAULT類型資源需要重新創建?

?

?

在D3D中有三大對象,他們是D3D OBJECT、D3D ADAPTERD3D DEVICE

D3D OBJECT很簡單,就是一個使用D3D功能的COM對象,其提供了創建DEVICE和枚舉ADAPTER的功能。

ADAPTER是對計算機圖形硬件和軟件性能的一個抽象,其包含了DEVICE。

DEVICE則是D3D的核心,它包裝了整個圖形流水管線,包括變換、光照和光柵化(著色);

?

DEVICE

根據D3D版本不同,流水線也有區別,比如最新的D3D10就包含了新的GS幾何處理。

圖形管線的所有功能由DRIVER提供。

而DIRVER分兩類,一種是GPU硬件DRIVER,另一種是軟件DRIVER。

這就是為什么在D3D中主要有兩類DEVICE, REF和HAL。

● 使用REF DEVICE時,圖形管線的光柵化功能由軟件DRIVER在CPU上模擬的,REF DEVICE從名字就可以看出這個給硬件廠商做功能參考用的,所以按常理它應該是全軟件實現,具備全部DX標準功能。

● 而使用HAL DEVICE時,RUNTIME則將使用HAL硬件層控制GPU來完成變換、光照和光柵化,而且只有HAL DEVICE中同時實現了硬件頂點處理和軟件頂點處理(REF DEVICE一般不能使用硬件頂點處理,除非自己在驅動上做手腳,比如PERFHUD)。

● 另外還有個一個不常用的SOFTWARE DEVICE,用戶可以使用DDI編寫自己的軟件圖形驅動,然后注冊進系統,之后便可在程序中使用。

?

Direct3D System Integration

檢查系統軟件硬件性能。
在程序的開始我們就要判斷目標機的性能,其主要流程是:

  • 確定要用的緩沖格式
  • GetAdapterCount()
  • GetAdapterDisplayMode
  • GetAdapterIdentifier //得到適配器描述
  • CheckDeviceType //判斷指定適配器上的設備是否支持硬件加速
  • GetDeviceCaps //指定設備的性能,主要判斷是否支持硬件頂點處理(T&L)
  • GetAdapterModeCount //得到適配器上指定緩沖格式所有可用的顯示模式
  • EnumAdapterModes //枚舉所有顯示模式
  • CheckDeviceFormat
  • CheckDeviceMultiSampleType
  • ?

    詳細使用請參考DX文檔。

    ?

    WINDOWS圖形系統的主要分為四層:

    ?

    • ● 圖形應用程序
    • ● D3D RUNTIME
    • ● SOFTWARE DRIVER
    • ● GPU

    ?

    此四層是按功能來分的,實際上他們之間界限并不如此明確,比如RUNTIME中其實也包含有USER MODE的SOFTWARE DRIVER,詳細結構這里不再多說。

    而在RUNTIME里有一個很重要的結構,叫做command buffer,當應用程序調用一個D3D API時,RUNTIME將調用轉換成設備無關的命令,然后將命令緩沖到這個COMMAND BUFFER中,這個BUFFER的大小是根據任務負載動態改變的,當這個BUFFER滿員之后,RUNTIME會讓所有命令FLUSH到KERNEL模式下的驅動中,而驅動中也是有一個BUFFER的,用來存儲已被轉換成的硬件相關的命令。

    D3D一般只允許其緩沖最多3個幀的圖形指令,而且RUNTIME和DRIVER都會被BUFFER中的命令做適當優化,比如我們在程序中連續設置同一個RENDER STATE,我們就會在調試信息中看到如下信息“Ignoring redundant SetRenderState - X”,這便是RUNTIME自動丟棄無用的狀態設置命令。

    在D3D9中可以使用QUERY機制來與GPU進行異步工作。

    所謂QUERY就是查詢命令,用來查詢RUNTIME、DRIVER或者GPU的狀態,D3D9中的QUERY對象有三種狀態

    ?

    • ● SIGNALED
    • ● BUILDING
    • ● ISSUED

    ?

    當他們處于空閑狀態后會將查詢狀態置于SIGNALED STATE,查詢分開始和結束,查詢開始表示對象開始記錄應用程序所需數據。

    當應用程序指定查詢結束后,如果被查詢的對象處于空閑狀態,則被查詢對象會將查詢對象置于SIGNALED狀態。

    GetData則是用來取得查詢結果,如果返回的是D3D_OK則結果可用,如果使用D3DGETDATA_FLUSH標志,表示將COMMAND BUFFER中的所有命令都發送到DRIVER。

    現在我們知道D3D API絕大部分都是同步函數,應用程序調用后,RUNTIME只是簡單的將其加入到COMMAND BUFFER,

    ?

    • ● 可能有人會疑惑我們如何測定幀率?
    • ● 又如何分析GPU時間呢?

    ?

    對于第一個問題我們要看當一幀完畢,也就是PRESENT()函數調用是否被阻塞,答案是可能被阻塞也可能不被阻塞,要看RUNTIME允許緩沖中存在的指令數量,如果超過額度,則PRESENT函數會被阻塞下來,如何PRESENT完全不被阻塞,當GPU執行繁重的繪制任務時,CPU工作進度會大大超過GPU,導致游戲邏輯快于圖形顯示,這顯然是不行的。

    測定GPU工作時間是件很麻煩的事,首先我們要解決同步問題,要測量GPU時間,首先我們必須讓CPU與GPU異步工作,在D3D9中可以使用QUERY機制做到這點,讓我們看看Accurately Profiling Driect3D API Calls中的例子:

    ?

    IDirect3DQuery9*?pQueryEvent;

    //1.創建事件類型的查詢事件
    m_pD3DDevice->CreateQuery(?D3DQUERYTYPE_EVENT,?&pQueryEvent);
    //2.在COMMAND?BUFFER中加入一個查詢結束的標記,此查詢默認開始于CreateDevice
    pQueryEvent->Issue(D3DISSUE_END);
    //3.將COMMAND?BUFFER中的所有命令清空到DRIVER中去,并循環查詢事件對象轉換到SIGNALED狀態,當GPU完成CB中所有命令后會將查詢事件狀態進行轉換。
    while(S_FALSE?==?pQueryEvent->GetData(?NULL,?0,?D3DGETDATA_FLUSH)?);
    LARGE_INTEGER?start,?stop;
    QueryPerformanceCounter(&start);?
    SetTexture();
    DrawPrimitive();?
    pQueryEvent->Issue(D3DISSUE_END);
    while(S_FALSE?==?pQueryEvent->GetData(?NULL,?0,?D3DGETDATA_FLUSH)?);
    QueryPerformanceCounter(&stop);?

    ?

    1.第一個GetData調用使用了D3DGETDATA_FLUSH標志,表示要將COMMAND BUFFER中的繪制命令都清空到DRIVER中去,當GPU處理完所有命令后會將這個查詢對象狀態置SIGNALED。
    2.將設備無關的SETTEXTURE命令加入到RUNTIME的COMMAND BUFFER中。
    3.將設備無關的DrawPrimitive命令加入到RUNTIME的COMMAND BUFFER中。
    4.將設備無關的ISSUE命令加入到RUNTIME的COMMAND BUFFER中。
    5.GetData會將BUFFER中的所有命令清空到DRIVER中去,注意這是GETDATA不會等待GPU完成所有命令的執行才返回。這里會有一個從用戶模式到核心模式的切換。
    6.等待DRIVER將所有命令都轉換為硬件相關指令,并填充到DRIVER BUFFER中后,調用從核心模式返回到用戶模式。
    7.GetData循環查詢 查詢對象 狀態。當GPU完成所有DRIVER BUFFER中的指令后會改變查詢對象的狀態。

    ?

    如下情況可能清空RUNTIME COMMAND BUFFER,并引起一個模式切換:

    ?

    1.Lock method(某些條件下和某些LOCK標志)
    2.創建設備、頂點緩沖、索引緩沖和紋理
    3.完全釋放設備、頂點緩沖、索引緩沖和紋理資源
    4.調用ValidateDevice
    5.調用Present
    6.COMMAND BUFFER已滿
    7.用D3DGETDATA_FLUSH調用GetData函數

    ?

    對于D3DQUERYTYPE_EVENT的解釋我不能完全理解(Query for any and all asynchronous events that have been issued from API calls)明白的朋友一定告訴我,只知道當GPU處理完D3DQUERYTYPE_EVENT類型查詢在CB中加入的D3DISSUE_END標記后,會將查詢對象狀態置SIGNALED狀態,所以CPU等待查詢一定是異步的。

    為了效率所以盡量少在PRESENT之前使用BEGINSCENE ENDSCENE對,為什么會影響效率?

    原因只能猜測,

    ?

    • ● 可能EndScene會引發Command buffer flush這樣會有一個執行的模式切換,
    • ● 也可能會引發D3D RUNTIME對MANAGED資源的一些操作。
    • ● 而且ENDSCENE不是一個同步方法,它不會等待DRIVER把所有命令執行完才返回。?

    ?

    D3D RUTIME的內存類型,分為3種,

    ?

    • ● VIDEO MEMORY(VM)
    • ● AGP MEMORY(AM)
    • ● SYSTEM MEMORY(SM)

    ?

    ?

    Memory

    ?

    • VM就是位于顯卡上的顯存,CPU只能通過AGP或PCI-E總線訪問到,讀寫速度都是非常慢的,CPU連續寫VM稍微快于讀,因為CPU寫VM時會在CACHE中分配32或64個字節(取決于CACHE LINE長度)的寫緩沖,當緩沖滿后會一次性寫入VM;
    • SM就是系統內存,CPU讀寫都非常快,因為SM是被CACHE到2級緩沖的,但GPU卻不能直接訪問到系統緩沖,所以創建在SM中的資源,GPU是不能直接使用的;
    • AM是最麻煩的一個類型,AM實際也存在于系統內存中,但這部分MEM不會被CPU CACHE,意味著CPU讀寫AM都會寫來個CACHE MISSING然后才通過內存總線訪問AM,所以CPU讀寫AM相比SM會比較慢,但連續的寫會稍微快于讀,原因就是CPU寫AM使用了“write combining”,而且GPU可以直接通過AGP或PCI-E總線訪問AM。

    ?

    ?

    ?

    所有D3D資源都創建在這3種內存之中,在創建資源時,我們可以指定如下存儲標志

    ?

    • ● D3DPOOL_DEFAULT
    • ● D3DPOOL_MANAGED
    • ● D3DPOOL_SYSTEMMEM
    • ● D3DPOOL_SCRATCH。

    ?

    ?

    如果我們使用D3DPOOL_DEFAULT來創建資源,則表示讓D3D RUNTIME根據我們指定的資源使用方法來自動使用存儲類型,一般是VM或AM,系統不會在其他地方進行額外備份,當設備丟失后,這些資源內容也會被丟失掉。但系統并不會在創建的時候使用D3DPOOL_SYSTEMMEM或D3DPOOL_MANAGED來替換它,注意他們是完全不同的POOL類型,創建到D3DPOOL_DEFAULT中的紋理是不能被CPU LOCK的,除非是動態紋理。但創建在D3DPOOL_DEFAULT中的VB IB RENDERTARGET BACK BUFFERS可以被LOCK。當你用D3DPOOL_DEFAULT創建資源時,如果顯存已經使用完畢,則托管資源會被換出顯存來釋放足夠的空間。

    ?D3DPOOL_SYSTEMMEM和D3DPOOL_SCRATCH都是位于SM中的,其差別是使用D3DPOOL_SYSTEMMEM時,資源格式受限于Device性能,因為資源很可能會被更新到AM或VM中去供圖形系統使用,但SCRATCH只受RUNTIME限制,所以這種資源無法被圖形系統使用。?

    D3DRUNTIME會優化D3DUSAGE_DYNAMIC 資源,一般將其放置于AM中,但不敢完全保證。另外為什么靜態紋理不能被LOCK,動態紋理卻可以,都關系到D3D RUNTIME的設計,在后面D3DLOCK說明中會敘述。

    ?

    D3DPOOL_MANAGED表示讓D3D RUNTIME來管理資源,被創建的資源會有2份拷貝,一份在SM中,一份在VM/AM中,創建的時候被放置L在SM,在GPU需要使用資源時D3D RUNTIME自動將數據拷貝到VM中去,當資源被GPU修改后,RUNTIME在必要時自動將其更新到SM中來,而在SM中修改后也會被UPDATE到VM去中。所以被CPU或者GPU頻發修改的數據,一定不要使用托管類型,這樣會產生非常昂貴的同步負擔。當LOST DEVICE發生后,RESET時RUNTIME會自動利用SM中的COPY來恢復VM中的數據,因為備份在SM中的數據并不是全部都會提交到VM中,所以實際備份數據可以遠多于VM容量,隨著資源的不斷增多,備份數據很可能被交換到硬盤上,這是RESET的過程可能變得異常緩慢,RUNTIME給每個MANAGED資源都保留了一個時間戳,當RUNTIME需要把備份數據拷貝到VM中時,RUNTIME會在VM中分配顯存空間,如果分配失敗,表示VM已經沒有可用空間,這樣RUNTIME會使用LRU算法根據時間戳釋放相關資源,SetPriority通過時間戳來設置資源的優先級,最近常用的資源將擁有高的優先級,這樣RUNTIME通過優先級就能合理的釋放資源,發生釋放后馬上又要使用這種情況的幾率會比較小,應用程序還可以調用EvictManagedResources強制清空VM中的所有MANAGED資源,這樣如果下一幀有用到MANAGED資源,RUNTIME需要重新載入,這樣對性能有很大影響,平時一般不要使用,但在關卡轉換的時候,這個函數是非常有用的,可以消除VM中的內存碎片。LRU算法在某些情況下有性能缺陷,比如繪制一幀所需資源量無法被VM裝下的時候(MANAGED),使用LRU算法會帶來嚴重的性能波動,如下例子:

    BeginScene();
    Draw(Box0);
    Draw(Box1);
    Draw(Box2);
    Draw(Box3);
    Draw(Circle0);
    Draw(Circle1);
    EndScene();
    Present();

    假設VM只能裝下其中5個幾何體的數據,那么根據LRU算法,在繪制Box3之前必須清空部分數據,那清空的必然是Circle0……,很顯然清空Box2是最合理的,所以這是RUNTIME使用MRU算法處理后續Draw Call能很好的解決性能波動問題,但資源是否被使用是按FRAME為單位來檢測的,并不是每個DRAW CALL都被記錄,每個FRAME的標志就是BEGINSCENE/ENDSCENE對,所以在這種情況下合理使用BEGINSCENE/ENDSCENE對可以很好的提高VM不夠情況下的性能。根據DX文檔的提示我們還可以使用QUERY機制來獲得更多關于RUNTIME MANAGED RESOURCE信息,但好像只在RUNTIME DEBUG模式下有用,理解RUNTIME如何MANAGE RESOURCE很重要,但編寫程序的時候不要將這些細節暴露出來,因為這些東西都是經常會變的。最后還要提醒的是,不光RUNTEIME會MANAGE RESOURCE,DRIVER也很可能也實現了這些功能,我們可以通過D3DCAPS2_CANMANAGERESOURCE標志取得DRIVER是否實現資源管理功能的信息,而且也可以在CreateDevice的時候指定D3DCREATE_DISABLE_DRIVER_MANAGEMENT來關閉DRIVER資源管理功能。??

    D3DLOCK探索D3D RUNTIME工作

    如果LOCK DEFAULT資源會發生什么情況呢?DEFAULT資源可能在VM或AM中,如果在VM中,必須在系統內容中開辟一個臨時緩沖返回給數據,當應用程序將數據填充到臨時緩沖后,UNLOCK的時候,RUNTIME會將臨時緩沖的數據傳回到VM中去,如果資源D3DUSAGE屬性不是WRITEONLY的,則系統還需要先從VM里拷貝一份原始數據到臨時緩沖區,這就是為什么不指定WRITEONLY會降低程序性能的原因。CPU寫AM也有需要注意的地方,因為CPU寫AM一般是WRITE COMBINING,也就是說將寫緩沖到一個CACHE LINE上,當CACHE LINE滿了之后才FLUSH到AM中去,第一個要注意的就是寫數據必須是WEAK ORDER的(圖形數據一般都滿足這個要求),據說D3DRUNTIME和NV DIRVER有點小BUG,就是在CPU沒有FLUSH到AM時,GPU就開始繪制相關資源產生的錯誤,這時請使用SFENCE等指令FLUSH CACHE LINE。第二請盡量一次寫滿一個CACHE LINE,否則會有額外延遲,因為CPU每次必須FLUSH整個CACHE LINE到目標,但如果我們只寫了LINE中部分字節,CPU必須先從AM中讀取整個LINE長數據COMBINE后重新FLUSH。第三盡可能順序寫,隨機寫會讓WRITE COMBINING反而變成累贅,如果是隨機寫資源,不要使用D3DUSAGE_DYNAMIC創建,請使用D3DPOOL_MANAGED,這樣寫會完全在SM中完成。

    普通紋理(D3DPOOL_DEFAULT)是不能被鎖定的,因為其位于VM中,只能通過UPDATESURFACE和UPDATETEXTURE來訪問,為什么D3D不讓我們鎖定靜態紋理,卻讓我們鎖定靜態VB IB呢?我猜測可能有2個方面的原因,第一就是紋理矩陣一般十分龐大,且紋理在GPU內部已二維方式存儲;第二是紋理在GPU內部是以NATIVE FORMAT方式存儲的,并不是明文RGBA格式。動態紋理因為表明這個紋理需要經常修改,所以D3D會特別存儲對待,高頻率修改的動態紋理不適合用動態屬性創建,在此分兩種情況說明,一種是GPU寫入的RENDERTARGET,一種是CPU寫入的TEXTURE VIDEO,我們知道動態資源一般是放置在AM中的,GPU訪問AM需要經過AGP/PCI-E總線,速度較VM慢許多,而CPU訪問AM又較SM慢很多,如果資源為動態屬性,意味著GPU和CPU訪問資源會持續的延遲,所以此類資源最好以D3DPOOL_DEFAULT和D3DPOOL_SYSTEMMEM各創建一份,自己手動進行雙向更新更好。千萬別 RENDERTARGET以D3DPOOL_MANAGED 屬性創建,這樣效率極低,原因自己分析。而對于改動不太頻繁的資源則推薦使用DEFAULT創建,自己手動更新,因為一次更新的效率損失遠比GPU持續訪問AM帶來的損失要小。?

    不合理的LOCK會嚴重影響程序性能,因為一般LOCK需要等待COMMAND BUFFER前面的繪制指令全部執行完畢才能返回,否則很可能修改正在使用的資源,從LOCK返回到修改完畢UNLOCK這段時間GPU全部處于空閑狀態,沒有合理使用GPU和CPU的并行性,DX8.0引進了一個新的LOCK標志D3DLOCK_DISCARD,表示不會讀取資源,只會全寫資源,這樣驅動和RUNTIME配合來了個瞞天過海,立即返回給應用程序另外塊VM地址指針,而原指針在本次UNLOCK之后被丟棄不再使用,這樣CPU LOCK無需等待GPU使用資源完畢,能繼續操作圖形資源(頂點緩沖和索引緩沖),這技術叫VB IB換名(renaming)。

    很多困惑來源于底層資料的不足,相信要是MS開放D3D源碼,開放驅動接口規范,NV / ATI顯示開放驅動和硬件架構信息,這些東西就很容易弄明白了。

    ?

    轉載于:https://www.cnblogs.com/wonderKK/archive/2011/11/29/2268394.html

    總結

    以上是生活随笔為你收集整理的Effulgent的《深入理解Direct3D9》整理版(转)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。