<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 剖析虛幻渲染體系(16)- 圖形驅動的秘密



    16.1 本篇概述

    16.1.1 本篇內容

    迄今為止,博主在博客中闡述的內容包含圖形API、GPU、游戲引擎、Shader、渲染技術、性能優化等等技術范疇內容,但似乎還未涉及圖形驅動的內幕。本篇將站在應用層開發者的視角,去闡述圖形驅動的相關技術內幕(如果是驅動開發者,則博主不認為是目標讀者),主要包含但不限于以下內容:

    • 圖形驅動的架構。
    • 圖形驅動的技術內幕。
    • 圖形驅動的常見實現。
    • 相關的硬件基礎。

    16.1.2 設備驅動概述

    要給“驅動”一詞下一個準確的定義是一個挑戰。從最基本的意義上講,驅動程序是一個軟件組件,它允許操作系統和設備相互通信。例如,假設應用程序需要從設備讀取一些數據,應用程序調用操作系統實現的函數,操作系統調用驅動程序實現的函數。該驅動程序由設計和制造該設備的同一家公司編寫,他們知道如何與設備硬件通信以獲取數據。驅動程序從設備獲取數據后,將數據返回給操作系統,操作系統將數據返回給應用程序。

    在計算機中,設備驅動程序是一種計算機程序,用于操作或控制連接到計算機或自動機的特定類型的設備。驅動程序為硬件設備提供軟件接口,使操作系統和其他計算機程序能夠訪問硬件功能,而無需知道所使用硬件的確切細節。

    驅動程序通過硬件連接的計算機總線或通信子系統與設備通信。當調用程序調用驅動程序中的例程時,驅動程序向設備發出命令(驅動設備)。一旦設備將數據發送回驅動程序,驅動程序就可以調用原始調用程序中的例程。驅動程序依賴于硬件且特定于操作系統,通常提供任何必要的異步時間相關硬件接口所需的中斷處理。

    設備驅動程序,特別是在現代Microsoft Windows平臺上,可以在內核模式(x86 CPU上的ring 0)或用戶模式(x86 CPU上的ring 3)下運行。在用戶模式下運行驅動程序的主要好處是提高了穩定性,因為寫得不好的用戶模式設備驅動程序不會通過覆蓋內核內存而導致系統崩潰。另一方面,用戶/內核模式轉換通常會帶來相當大的性能開銷,從而使內核模式驅動程序成為低延遲網絡的首選。

    用戶模塊只能通過使用系統調用來訪問內核空間,最終用戶程序(如UNIX shell或其他基于GUI的應用程序)是用戶空間的一部分,這些應用程序通過內核支持的函數與硬件交互。

    常見的設備驅動包含但不限于:

    • 打印機。
    • 視頻適配器。
    • 網卡。
    • 聲卡。
    • 各種類型的本地總線,特別是用于在現代系統上控制總線。
    • 各種低帶寬輸入/輸出總線(用于鼠標、鍵盤等定點設備)。
    • 計算機存儲設備,如硬盤、CD-ROM和軟盤總線(ATA、SATA、SCSI、SAS)。
    • 實現對不同文件系統的支持。
    • 圖像掃描儀。
    • 數碼相機。
    • 數字地面電視調諧器。
    • 用于無線個人局域網的射頻通信收發器適配器,用于家庭自動化中的短距離低速無線通信(例如藍牙低能量(BLE)、線程、ZigBee和Z-Wave)。
    • IrDA適配器。

    以上的解釋在以下幾個方面過于簡單:

    • 并非所有驅動程序都必須由設計該設備的公司編寫。在許多情況下,設備是根據已發布的硬件標準設計的,意味著驅動程序可以由Microsoft編寫,并且設備設計器不必提供驅動程序。

    • 并非所有驅動程序都與設備直接通信。對于給定的I/O請求(如從設備讀取數據),通常有多個驅動程序參與請求,這些驅動程序分層在驅動程序堆棧中。可視化堆棧的傳統方法是,第一個參與者在頂部,最后一個參與者在底部,如下圖所示。堆棧中的一些驅動程序可能通過將請求從一種格式轉換為另一種格式來參與。這些驅動程序不直接與設備通信,他們只是操縱請求并將請求傳遞給堆棧中較低的驅動程序。

      其中功能驅動程序是堆棧中直接與設備通信的一個驅動程序,過濾驅動程序是執行輔助處理的驅動程序。

    • 一些過濾驅動程序觀察并記錄有關輸入/輸出請求的信息,但不主動參與這些請求。例如,某些過濾驅動程序充當驗證器,以確保堆棧中的其他驅動程序正確處理I/O請求。

    可以通過說驅動程序是觀察或參與操作系統和設備之間通信的任何軟件組件來擴展驅動程序的定義。

    至此,擴展定義相當準確,但仍然不完整,因為一些驅動程序根本與任何硬件設備都沒有關聯。例如,假設需要編寫一個能夠訪問核心操作系統數據結構的工具,而只有在內核模式下運行的代碼才能訪問核心操作系統數據結構。可以通過將該工具拆分為兩個組件來實現這一點,第一個組件以用戶模式運行并顯示用戶界面,第二個組件以內核模式運行,可以訪問核心操作系統數據。在用戶模式下運行的組件稱為應用程序,在內核模式下運行的組件稱為軟件驅動程序,軟件驅動程序與硬件設備不關聯。下圖說明了與內核模式軟件驅動程序通信的用戶模式應用程序。

    軟件驅動程序總是在內核模式下運行,編寫軟件驅動程序的主要原因是為了訪問僅在內核模式下可用的受保護數據。然而,設備驅動程序并不總是需要訪問內核模式的數據和資源。因此,一些設備驅動程序以用戶模式運行。

    此外,還有總線驅動等更多功能性驅動(下圖)。

    16.1.3 圖形驅動概述

    顯示驅動程序是允許操作系統與圖形硬件一起工作的軟件。圖形硬件控制顯示器,可以是計算機中的擴充卡,也可以內置在計算機的主電路板中(如筆記本電腦),也可以駐留在計算機外部(如Matrox remote graphics units)。每種型號的圖形硬件都是不同的,需要一個顯示驅動程序來與系統的其余部分連接。具有不同特性的較新圖形硬件型號不斷發布,每種新型號的控制方式往往不同。

    驅動程序將操作系統函數調用(命令)轉換為特定于該設備的調用。對于同一型號的圖形硬件,使用不同函數調用的每個操作系統也需要不同的顯示驅動程序。例如,Windows XP和Linux需要非常不同的顯示驅動程序。但是,同一操作系統的不同版本有時可以使用相同的顯示驅動程序。例如,Windows 2000和Windows XP的顯示驅動程序通常是相同的。

    如果未安裝特定于計算機圖形硬件的顯示驅動程序,圖形硬件將無法使用或功能有限。如果特定于型號的顯示驅動程序不可用,操作系統通常可以使用具有基本功能的通用顯示驅動程序。例如,Windows在“安全模式”下使用通用VGA或SVGA顯示驅動程序。在這種情況下,大多數特定于模型的功能都不可用。

    由于圖形硬件非常復雜,并且顯示驅動程序非常特定于該硬件,因此顯示驅動程序通常由硬件制造商創建和維護,甚至操作系統中包含的顯示驅動程序也通常最初由制造商提供。制造商可以完全訪問有關硬件的信息,并在確保以最佳方式使用其硬件方面擁有既得利益。

    顯示驅動程序對系統資源具有低級(內核級)訪問權限,因為顯示驅動程序需要直接與圖形硬件通信,這種低級別訪問使得顯示驅動程序的編碼更加仔細和可靠。顯示驅動程序中的錯誤比應用程序軟件中的錯誤更有可能使整個操作系統暫時無法使用。

    幸運的是,某些公司或組織(如Matrox)憑借其對專業用戶的傳統承諾以及其產品的長期產品生命周期,在制造可靠的顯示器驅動程序方面享有盛譽。長生命周期意味著其顯示器驅動程序的開發將持續更長的時間,使得懸而未決的問題更有可能得到解決,并且顯示驅動程序能夠適應不斷變化的軟件環境。新的操作系統和新的應用軟件正在不斷發布,每一種都可能需要新的驅動程序版本來保持兼容性或提供新的功能,可用的最新顯示驅動程序經常解決此類問題。長的產品生命周期也使得顯示驅動程序更有可能添加新的特性和功能,而不考慮操作系統和應用程序軟件。

    對于Linux等開源操作系統,非制造商有時會維護顯示驅動程序,操作系統的開源特性使得為此類操作系統編寫代碼變得更容易(但并不容易)。雖然Matrox為特殊目的維護自己的Linux顯示驅動程序,但Matrox Millennium G系列產品提供了基本的開源Linux顯示驅動程序,Matrox合作伙伴Xi Graphics(一家在Linux/Unix開發方面有10多年經驗的公司)為Matrox產品提供了全功能的Linux顯示驅動程序。

    即使對于相同的操作系統和圖形硬件型號,有時也會同時提供不同的顯示驅動程序,以滿足不同的需求。以下是Matrox圖形硬件某些型號可用的不同顯示驅動程序的摘要:

    • “HF”驅動程序:此類驅動程序具有豐富的界面,需要Microsoft .NET Framework軟件,適用于喜歡此界面或者已有此界面的用戶,微軟NET軟件包含在許多最近安裝的Windows和許多其他需要它的應用程序中。“HF”軟件僅適用于某些型號的Parhelia系列產品和Millennium P系列產品。
    • Microsoft .NET Framework:是由Microsoft創建的一種編程基礎架構,用于構建、部署和運行用戶使用的應用程序和服務.NET技術,如我們的HF驅動程序。
    • 統一驅動程序:此類驅動程序一次支持多個不同型號的Matrox產品。對于需要一次為許多不同的Matrox產品安裝顯示驅動程序并希望從一個軟件包進行安裝的系統管理員非常有用,對于不確定自己的圖形硬件型號的用戶也很有用。由于統一驅動程序的接口必須支持不同的硬件,Matrox統一驅動程序使用支持更廣泛的“SE”接口。
    • XDDM:Windows XP顯示驅動程序模型,有時被稱為XPDM或XPDDM。
    • WDDM:Windows顯示驅動程序模型(WDDM)是Windows Vista、Server 2008和Windows 7支持的顯示驅動程序體系結構。
    • “WDM”(Windows驅動程序型號)驅動程序包:此類驅動程序適用于需要視頻捕獲和實時播放功能的用戶,有一個需要Microsoft的豐富界面.NET Framework軟件和支持視頻的額外功能,微軟NET軟件包含在許多最近安裝的Windows和許多其他需要它的應用程序中。僅適用于某些型號的Parhelia系列產品。
    • WHQL驅動程序:“Windows硬件質量實驗室”驅動程序接受Microsoft開發的一系列標準測試,以提高驅動程序的可靠性。Matrox等硬件供應商執行這些測試,并將結果提交給Microsoft進行認證。如果用戶安裝的驅動程序未通過WHQL認證(即使驅動程序通過其他方式認證),則Windows操作系統的最新版本會向用戶發出警告。為了避免此類警告和WHQL流程提供的額外測試,系統管理員通常更喜歡使用WHQL驅動程序。
    • 認證驅動程序:Matrox通過領先的專業2D/3D軟件認證顯示驅動程序,包括用于AEC、MCAD、GIS和P&P(工廠和工藝設計)的軟件,這種軟件通常要求很高,并廣泛使用圖形硬件加速。Matrox單獨認證此類應用程序,以確保額外的可靠性。該測試是對所有Matrox顯示器驅動程序進行的常規測試的補充。
    • ISV認證驅動程序:某些“獨立軟件供應商”對其應用軟件有自己的認證流程,與Matrox認證的驅動程序相似,因為特定圖形硬件的顯示驅動程序會在特定應用程序中進行額外測試。但是,在這種情況下,Matrox將硬件和顯示驅動程序提交給ISV,ISV執行測試。與Matrox認證相比,ISV認證的頻率較低,涵蓋的應用較少。
    • 測試版驅動程序:有時,特殊支持或修復程序首先作為“測試版”驅動程序發布。這類驅動已經接受了一些測試,但不一定完成了整個測試周期,測試版驅動程序可供想要測試或預覽重要新功能的用戶使用。
    • ......

    設備驅動程序是操作系統內核代碼的最大貢獻者,Linux內核中有超過500萬行代碼,并且會導致嚴重的復雜性、bug和開發成本。近年來,出現了一系列旨在提高可靠性和簡化驅動開發的研究。然而,除了用于研究的一小部分驅動程序之外,人們對這一龐大的代碼體的構成知之甚少。

    有學者研究Linux驅動程序的源代碼,以了解驅動程序的實際用途、當前的研究如何應用于這些驅動程序以及未來的研究機會,大體上,研究著眼于驅動程序代碼的三個方面:

    • 驅動程序代碼功能的特征是什么,驅動程序研究如何適用于所有驅動程序。
    • 驅動程序如何與內核、設備和總線交互。
    • 是否存在可抽象為庫以減少驅動程序大小和復雜性的相似性?

    從驅動程序交互研究中,發現USB總線提供了一個高效的總線接口,具有重要的標準化代碼和粗粒度訪問,非常適合隔離執行驅動程序。此外,不同總線和級別的驅動程序的設備交互水平差異很大,這表明隔離成本將因級別而異。

    設備驅動程序是在操作系統和硬件設備之間提供接口的軟件組件,驅動程序配置和管理設備,并將來自內核的請求轉換為對硬件的請求。驅動程序依賴于三個接口:

    • 驅動程序和內核之間的接口,用于通信請求和訪問操作系統服務。
    • 驅動器和設備之間的接口,用于執行操作。
    • 驅動器和總線之間的接口,用于管理與設備的通信。

    下圖顯示了Linux中驅動程序根據其接口的層次結構,從基本驅動程序類型開始,即char、block和net,可確定72個獨特的驅動程序類別。大多數(52%)驅動程序代碼是字符驅動程序,分布在41個類中。網絡驅動程序占驅動程序代碼的25%,但只有6個類。例如,視頻和GPU驅動程序對驅動程序代碼的貢獻很大(近9%),這是因為復雜的設備具有每代都會改變的指令集,但這些設備由于其復雜性,在很大程度上被驅動程序研究所忽視。

    Linux驅動程序在基本驅動程序類方面的分類。其中提到了5個最大類的大小(以代碼行的百分比表示)。

    通常認為設備驅動程序主要執行輸入/輸出。標準本科操作系統教科書規定:設備驅動程序可以看作是轉換器,其輸入由高級命令組成,如“檢索塊123”其輸出由硬件控制器使用的低級別、特定于硬件的指令組成,硬件控制器將輸入/輸出設備連接到系統的其余部分。

    隨著設備功能越來越強大,并擁有自己的處理器,人們通常認為驅動程序執行的處理很少,只是在操作系統和設備之間傳輸數據。然而,如果驅動程序需要大量的CPU處理,例如計算RAID奇偶校驗、網絡校驗和或視頻驅動程序顯示數據,則必須保留處理能力。15%的驅動程序至少有一個執行處理的函數,而處理發生在所有驅動程序函數中的1%。

    下表比較了PCI、USB和XenBus所有設備類別的復雜性指標。通過比較一個驅動程序支持的芯片組數量來考察支持多個設備的效率,表明了支持新設備的復雜性,以及驅動程序的抽象級別。支持來自不同供應商的許多芯片組的驅動程序表示具有高水平通用功能的標準化接口。相比之下,支持單個芯片組的驅動程序效率較低,因為每個設備都需要一個單獨的驅動程序。

    三種總線(bus)的驅動效率差別很大。PCI驅動程序支持每個驅動程序7.5個芯片組,幾乎總是來自同一供應商。相比之下,USB驅動程序的平均值為13.2,通常來自許多供應商。其中很大一部分差異在于USB協議的標準化,而許多PCI設備都不存在這種標準化。例如,USB存儲設備實現標準接口。因此,主USB存儲驅動程序代碼在很大程度上很常見,但包括對設備特定代碼的調用。此代碼包括特定于設備的初始化、掛起/恢復(未提供通過USB存儲并作為附加功能要求保留)和其他需要設備特定代碼的例程。雖然USB驅動程序有了更大的標準化工作,但仍不完整。

    所有現代操作系統中驅動程序的另一個關鍵要求是需要多路訪問設備。例如,一個磁盤控制器驅動程序必須允許多個應用程序同時讀寫數據,即使這些應用程序沒有其他關聯。這一要求會使驅動程序設計復雜化,因為它增加了對多個獨立線程之間同步的需要。研究驅動程序如何跨長延遲操作多路訪問:它們傾向于線程化代碼、在堆棧上保存狀態并阻止事件,還是傾向于事件驅動代碼、將回調注冊為USB驅動程序的完成例程或PCI設備的中斷處理程序和計時器。如果驅動程序被移動到內核之外,驅動程序和內核將使用通信通道相互通信,支持事件驅動并發可能更自然。

    下圖中標記為事件友好型和線程化的條形圖中顯示的結果表明,線程化和事件友好型代碼的劃分在驅動程序類中差異很大。總的來說,驅動程序出于不同的目的廣泛使用這兩種同步方法。驅動程序使用線程原語來同步驅動程序和設備操作,同時初始化驅動程序并更新驅動程序全局數據結構,而事件友好型代碼用于核心I/O請求。

    線程化和事件友好型同步原語覆蓋范圍內的驅動程序入口點的百分比。

    隨著GPU性能的提高,對圖形驅動程序的要求也越來越高。良好的用戶體驗取決于穩定、可靠的軟件棧。


    16.2 圖形驅動基礎

    16.2.1 硬件概覽

    稍早期(如2012年)的計算機硬件架構大多可抽象成如下摸樣:

    其中一款顯卡的結構見下圖,包含了GPU(執行所有計算)、視頻輸出(連接到屏幕)、顯存(存儲紋理或通用數據)、電源管理(降低電壓,調節電流)、主機交互總線(與CPU的通信)等部件:

    如今,所有計算機的結構都是類似的:一個中央處理器和許多外圍設備。為了交換數據,這些外圍設備通過總線互連,所有通信都通過總線進行。下圖概述了標準計算機中外圍設備的布局。

    典型計算機中的外圍互連。

    總線的第一個用戶是CPU。CPU使用總線訪問系統內存和其他外圍設備。然而,CPU并不是唯一能夠向外圍設備寫入和讀取數據的設備,外圍設備本身也具有直接交換信息的能力。具體地說,能夠在沒有CPU干預的情況下讀取和寫入存儲器的外圍設備被稱為具有DMA(直接存儲器訪問)能力,并且存儲器事務通常被稱為DMA。這種類型的事務很有趣,因為它允許驅動程序使用GPU而不是CPU來進行內存傳輸。由于CPU不再需要主動工作來實現這些傳輸,并且由于它允許CPU和GPU之間更好的異步性,因此可以獲得更好的性能。DMA的常見用途包括提高紋理上傳或流視頻的性能。如今,所有圖形處理器都具有這種能力(稱為DMA總線主控),這種能力包括視頻卡請求并隨后控制總線幾微秒。

    如果外設能夠在不連續的內存頁列表中實現DMA(當數據在內存中不連續時非常方便),則稱其具有DMA分散-收集(scatter-gather)功能(因為它可以將數據分散到不同的內存頁,或從不同的內存頁收集數據)。

    請注意,DMA功能在某些情況下可能是一個缺點。例如,在實時系統上,意味著當DMA事務正在進行時,CPU無法訪問總線,并且由于DMA事務是異步發生的,可能導致錯過實時調度截止時間。另一個例子是小型DMA內存傳輸,其中設置DMA的CPU開銷大于異步增益,導致傳輸速度變慢。因此,雖然DMA從性能角度來看有很多優勢,但在某些情況下應該避免。

    另外,GPU需要主機:

    • 設置屏幕模式/分辨率(模式設置)。
    • 配置引擎和通信總線。
    • 處理電源管理。熱量管理(風扇,對過熱/功率作出反應),更改GPU的頻率/電壓以節省電源。
    • 處理數據。分配處理上下文(GPU VM+上下文ID),上傳紋理或場景數據,發送要在上下文中執行的命令。

    16.2.2 總線類型

    總線將機器外圍設備連接在一起,不同外設之間的每一次通信都通過(至少)一條總線進行。特別是,總線是大多數圖形卡連接到計算機其余部分的方式(一個顯著的例外是某些嵌入式系統,其中GPU直接連接到CPU)。如下表所示,有許多適用于圖形的總線類型:PCI、AGP、PCI-X、PCI express等等。本小節將詳細介紹的所有總線類型都是PCI總線類型的變體,但其中一些總線在原始PCI設計上有獨特的改進。

    • PCI (Peripheral Component Interconnect,外設部件互連標準):PCI是目前允許連接圖形外圍設備的最基本的總線。它的一個關鍵特性叫做總線控制,此功能允許給定的外圍設備在給定的周期數內占用總線并執行完整的事務(稱為DMA,直接內存訪問)。PCI總線是一致的,意味著無需顯式刷新即可使內存在設備間保持一致。

    • AGP (Accelerated Graphics Port,圖形加速端口):AGP本質上是一種經過改進的PCI總線,與它的祖先相比,具有許多額外的功能。最重要的是,它的速度更快,主要得益于更高的時鐘速度以及在每個時鐘tick中每個通道發送2、4或8位的能力(分別適用于AGP 2x、4x和8x)。AGP還有三個顯著特點:

      • 第1個特性是AGP GART(圖形光圈重映射表),是IOMMU的一種簡單形式。它允許從系統內存中取出一組(非連續的)物理內存頁,并將其暴露給GPU作為連續區域使用,以很低的成本增加了GPU可用的內存量,并為CPU和GPU之間共享數據創建了一個方便的區域(AGP圖形卡可以在該區域進行快速DMA,并且由于GART區域是一塊系統RAM,因此CPU訪問比VRAM快得多)。一個顯著的缺點是,GART區域不一致,因此在另一方開始傳輸之前,需要刷新對GART的寫入(無論是從GPU還是CPU)。另一個缺點是,硬件只處理一個GART區域,它必須由驅動程序分配。
      • 第2個特性是AGP邊帶尋址(SBA)。邊帶尋址由用作地址總線的8個額外總線位組成,與多路復用地址和數據之間的總線帶寬不同,標準AGP帶寬只能用于數據。此功能對驅動程序開發人員是透明的。
      • 第3個特性是AGP快速寫入(FW)。快速寫入允許直接向圖形卡發送數據,而無需圖形卡啟動DMA。此功能對驅動程序開發人員也是透明的。

      注意,后兩個功能在各種硬件上都不穩定,通常需要特定于芯片組的hack才能正常工作,因此建議不啟用它們。事實上,它們是AGP卡上出現奇怪硬件錯誤的極為常見的原因。

    • PCI-X:PCI-X是為服務器板開發的一種更快的PCI,這種格式的圖形外圍設備很少(一些Matrox G550卡)。不要將其與PCI-Express混淆,后者的使用非常廣泛。

    • PCI-Express (PCI-E):PCI Express是新一代PCI設備,比簡單的改進PCI有更多的優點。最后,需要注意的是,根據體系結構,CPU-GPU通信并不總是依賴于總線,在GPU和CPU位于單個芯片上的嵌入式系統上尤其常見。在這種情況下,CPU可以直接訪問GPU寄存器。

    16.2.3 顯存架構

    雖然DRAM通常被視為一個扁平的字節數組,但其內部結構要復雜得多。對于像GPU這樣的高性能應用程序,非常有必要深入地理解它。從下往上大致看,VRAM由以下部分組成:

    • R行乘以C列的內存平面(memory plane),每個單元為一位。

    • 由32、64或128個并行使用的內存平面組成的內存組(memory bank)——這些平面通常分布在多個芯片上,其中一個芯片包含16或32個內存平面。bank中的所有頁面都連接到行尋址系統(列也是如此),并且這些頁面由命令信號和每行/列的地址控制。bank中的行和列越多,地址中需要使用的位就越多。

    • 由若干個[2、4或8]個memory bank連接在一起并由地址位選擇的內存排(memory rank)——給定內存平面的所有memory bank位于同一芯片中。

    • 由一個或兩個連接在一起并由芯片選擇線選擇的memory rank組成的內存子分區(memory subpartition)——rank的行為類似于bank,但不必具有統一的幾何結構,而是在單獨的芯片中。

    • 由一個或兩個稍微獨立的memory subpartition組成了內存分區(memory partition)

    • 整個VRAM由幾個[1-8]個memory partition組成。

    以上數量會因不同的GPU架構和家族而不同。

    DRAM最基本的單元是內存平面,它是按所謂的列和行組織的二維位數組:

         column
    row  0  1  2  3  4  5  6  7
    0    X  X  X  X  X  X  X  X
    1    X  X  X  X  X  X  X  X
    2    X  X  X  X  X  X  X  X
    3    X  X  X  X  X  X  X  X
    4    X  X  X  X  X  X  X  X
    5    X  X  X  X  X  X  X  X
    6    X  X  X  X  X  X  X  X
    7    X  X  X  X  X  X  X  X
    
    buf  X  X  X  X  X  X  X  X
    

    內存平面包含一個緩沖區,該緩沖區可容納整個行。在內部,DRAM通過緩沖區以行為單位進行讀/寫。因此有幾個后果:

    • 在對某個位進行操作之前,必須將其行加載到緩沖區中,會很慢。
    • 處理完一行后,需要將其寫回內存數組,也很慢。
    • 因此,訪問新行的速度很慢,如果已經有一個活動行,訪問速度甚至更慢。
    • 在一段不活動時間后,搶先關閉一行通常很有用——這種操作稱為precharging(預充電?)一個bank。
    • 但是,可以快速訪問同一行中的不同列。

    由于加載列地址本身比實際訪問活動緩沖區中的位花費更多的時間,所以DRAM是以突發方式訪問的,即對活動行中1-8個相鄰位的一系列訪問。通常,突發中的所有位都必須位于單個對齊的8位組中。內存平面中的行和列的數量始終是2的冪,并通過行選擇和列選擇位的計數來衡量[即行/列計數的log2],通常有8-10列位和10-14行位。內存平面被組織在bank中,bank由兩個內存平面的冪組成。內存平面是并行連接的,共享地址和控制線,只有數據/數據啟用線是分開的。這有效地使內存bank類似于由32位/64位/128位內存單元組成的內存平面,而不是單個位——適用于平面的所有規則仍然適用于bank,但操作的單元比位大。單個存儲芯片通常包含16或32個存儲平面,用于單個bank,因此多個芯片通常連接在一起以形成更寬的bank。

    一個內存芯片包含多個[2、4或8]個bank,使用相同的數據線,并通過bank選擇線進行多路復用。雖然在bank之間切換比在一行中的列之間切換要慢一些,但要比在同一bank中的行之間切換快得多。因此,一個內存bank由(MEMORY_CELL_SIZE / MEMORY_CELL_SIZE_PER_CHIP)存儲器芯片組成。一個或兩個通過公共線(包括數據)連接的內存列,芯片選擇線除外,構成內存子分區。在rank之間切換與在bank中的列組之間切換具有基本相同的性能后果,唯一的區別是物理實現和為每個rank使用不同數量行選擇位的可能性(盡管列計數和列計數必須匹配)。存在多個bank/rank的后果:

    • 確保一起訪問的數據要么屬于同一行,要么屬于不同的bank,這一點很重要(以避免行切換)。
    • 分塊內存布局的設計使分塊大致對應于一行,相鄰的分塊從不共享一個bank。

    內存子分區在GPU上有自己的DRAM控制器。1或2個子分區構成一個內存分區,它是一個相當獨立的實體,具有自己的內存訪問隊列、自己的ZROP和CROP單元,以及更高版本卡上的二級緩存。所有內存分區與crossbar邏輯一起構成了GPU的整個VRAM邏輯,分區中的所有子分區必須進行相同的配置,GPU中的分區通常配置相同,但在較新的卡上則不是必需的。子分區/分區存在的后果:

    • 與bank一樣,可以使用不同的分區來避免相關數據的行沖突。
    • 與bank不同,如果(子)分區沒有得到同等利用,帶寬就會受到影響。因此,負載平衡非常重要。

    雖然內存尋址高度依賴于GPU系列,但這里概述了基本方法。內存地址的位按順序分配給:

    • 識別內存單元中的字節,因為無論如何都必須訪問整個單元。
    • 多個列選擇位,以允許突發(burst)。
    • 分區/子分區選擇-以低位進行,以確保良好的負載平衡,但不能太低,以便在單個分區中保留相對較大的tile,以利于ROP。
    • 剩余列選擇位。
    • 所有/大部分bank選擇位,有時是排名選擇位,以便相鄰地址不會導致行沖突。
    • 行位。
    • 剩余的bank位或rank位,有效地允許將VRAM拆分為兩個區域,在其中一個區域放置顏色緩沖區,在另一個區域放置zeta緩沖區,這樣它們之間就不會有行沖突。

    此外,可以不同倍數的數據速率同步動態隨機存取內存,正是我們所熟知的DDR-SDRAM或DDR:

    單ank和雙rank對比。

    單速率、雙速率、四速率對比圖。

    下圖是CPU和GPU內存請求路線:

    GTT/GART作為CPU-GPU共享緩沖區用于通信:

    16.2.4 虛擬和物理內存

    內存有兩種主要的不同含義:

    • 物理內存。物理內存是真實的硬件內存,存儲在內存芯片中。

    • 虛擬內存。虛擬內存是物理內存地址的轉換,允許用戶空間應用程序查看其分配的塊,就好像它們是連續的,而它們在芯片上是碎片化和分散的。

      虛擬地址空間的一些關鍵功能,以及虛擬內存和物理內存的關系。

    一些操作系統(如Windows)還存在分頁緩沖池(paged pool)非分頁緩沖池(nonpaged pool)的機制。在用戶空間中,所有物理內存頁面都可以根據需要調出到磁盤文件。 在系統空間中,某些物理頁面可以調出,而其他物理頁面則不能。 系統空間具有用于動態分配內存的兩個區域:分頁緩沖池和非分頁緩沖池。分頁緩沖池中分配的內存可以根據需要調出到磁盤文件,非分頁緩沖池中分配的內存永遠無法調出到磁盤文件。

    為了簡化編程,更容易處理連續的內存區域。分配一個小的連續區域很容易,但分配一個更大的內存塊將需要同樣多的連續物理內存,在啟動后由于內存碎片化導致難以實現。因此,需要一種機制來保持應用程序的連續內存塊外觀,同時使用分散的內存塊。

    為了實現這一點,內存被拆分為多個頁。就本文的范圍而言,可以說內存頁是物理內存中連續字節的集合,以便使分散的物理頁列表在虛擬空間中看起來是連續的,一個稱為MMU(內存映射單元)的硬件使用頁表將虛擬地址(用于應用程序)轉換為物理地址(用于實際訪問內存),如下圖所示。如果頁面不存在于虛擬空間中(因此不在MMU表中),MMU可以向其發送信號,為報告對不存在內存區域的訪問提供了基本機制。反過來又被系統用來實現高級內存編程,如交換或動態頁面實例化。由于MMU僅對CPU訪問內存有效,虛擬地址與硬件無關,因為無法將它們與物理地址匹配。

    MMU和IOMMU。

    雖然MMU只適用于CPU訪問,但它對外圍設備有一個等價物:IOMMU。如上圖所示,IOMMU與MMU相同,只是它虛擬化了外圍設備的地址空間。IOMMU可以在主板芯片組(在這種情況下,它在所有外圍設備之間共享)或圖形卡本身(在圖形卡本身上,它將被稱為AGP GART、PCI GART)上看到各種化身。IOMMU的工作是將外圍設備的內存地址轉換為物理地址。特別是,它允許“欺騙”設備將其DMA限制在給定的內存范圍內,是更好的安全性和硬件虛擬化所必需的。

    IOMMU的一個特例是Linux swiotlb,它在引導時分配一段連續的物理內存(使得有一個大的連續物理分配是可行的,因為還沒有碎片),并將其用于DMA。由于內存在物理上是連續的,不需要頁轉換,因此可以在該內存范圍內進行DMA。但是,意味著該內存(默認為64MB)是預先分配的,不會用于其他任何用途。

    AGP GART是IOMMU的另一個特例,它與AGP圖形卡一起使用,向圖形卡顯示一個線性區域。在這種情況下,IOMMU被嵌入主板上的AGP芯片組中。AGP GART區域作為虛擬內存的線性區域向系統公開。

    IOMMU的另一個特例是某些GPU上的PCI GART,它允許向卡公開一塊系統內存。在這種情況下,IOMMU表嵌入到圖形卡中,通常使用的物理內存不需要是連續的。

    顯然,有這么多不同的內存類型,性能是不均勻的,并非所有的訪問組合都是快速的,主要取決于它們是否涉及CPU、GPU或總線傳輸。另一個問題是內存一致性:如何確保跨設備的內存一致,尤其是CPU寫入的數據可供GPU使用(或相反)。這兩個問題是相關的,因為較高的性能通常意味著較低水平的內存連貫性,反之亦然。

    就設置內存緩存參數而言,有兩種方法可以在內存范圍上設置緩存屬性:

    • MTRR。MTRR(內存類型范圍寄存器)是描述給定物理內存范圍屬性的寄存器。每個MTRR包含一個起始物理地址、一個大小和一個緩存類型。MTRR的數量取決于系統,但非常有限。雖然適用于物理內存范圍,但效果會作用于相應的虛擬內存頁。例如,可以使用特定的緩存類型映射頁面。
    • PAT(頁面屬性表)允許設置每頁內存屬性。與MTRR一樣依賴有限數量的內存范圍不同,可以在每頁的基礎上指定緩存屬性。但是,它是僅在最新x86處理器上可用的擴展。

    除此之外,可以在某些體系結構上使用顯式緩存指令,例如在x86上,movntq是一條未緩存的mov指令,clflush可以選擇性地刷新緩存行。

    有三種緩存模式,可通過MTRR和PAT系統內存使用:

    • UC(UnCached)內存未緩存。CPU對此區域的讀/寫是未緩存的,每個內存寫入指令都會觸發實際的即時內存寫入。有助于確保信息已實際寫入,以避免CPU/GPU爭用的情況。
    • WC(Write Combine)內存未緩存,但CPU寫入被組合在一起以提高性能。在需要未緩存內存但將寫操作組合在一起不會產生不利影響的情況下,非常有利于提高性能。
    • WB(Write Back)內存已被緩存。是默認模式,可以獲得CPU訪問的最佳性能。然而,并不能確保內存寫入在有限時間后傳播到中央內存。

    請注意,以上緩存模式僅適用于CPU,GPU訪問不受當前緩存模式的直接影響。然而,當GPU必須訪問之前由CPU填充的內存區域時,未緩存模式可確保實際完成內存寫入,并且不會掛起在CPU緩存中。實現相同效果的另一種方法是使用某些x86處理器(如cflush)上的緩存刷新指令,但比使用緩存模式的可移植性差。另一種(可移植的)方法是使用內存屏障,它可以確保在繼續之前將掛起的內存寫入提交到主內存。

    顯然,有這么多不同的緩存模式,并非所有訪問都具有相同的性能:

    • 在CPU訪問系統內存時,未緩存模式提供最差的性能,回寫提供最好的性能,寫入組合介于兩者之間。
    • 當CPU從離散卡訪問視頻內存時,所有訪問都非常慢,無論是讀還是寫,因為每次訪問都需要總線上的一個周期。因此,不建議使用CPU訪問大面積的VRAM。此外,在某些GPU上需要同步,否則可能會導致GPU掛起。
    • 顯然,GPU訪問VRAM的速度非常快。
    • GPU對系統RAM的訪問不受緩存模式的影響,但仍須通過總線,DMA事務就是這種情況。由于都是異步發生的,從CPU的角度來看,它們可以被視為“免費”,但是每個DMA事務都涉及到不可忽略的設置成本。這就是為什么在傳輸少量內存時,DMA事務并不總是優于直接CPU訪問。

    最后,關于內存的最后一個重要觀點是內存屏障和寫入賬本(write posting)的概念。對于緩存(寫合并或寫回)內存區域,內存屏障可確保掛起的寫操作實際上已提交到內存。例如,在要求GPU讀取給定的內存區域之前,會使用此選項。對于I/O區域,存在一種類似的稱為寫入賬本的技術:包括在I/O區域內進行虛擬讀取,作為一種副作用,它會等到掛起的寫入生效后再完成。

    經典的桌面計算機體系結構,在PCI Express上具有獨特的圖形卡。給定內存技術的典型帶寬缺少內存延遲,GPU和CPU之間不可能實現零拷貝,因為兩者都有各自不同的物理內存,數據必須從一個復制到另一個才能共享。

    帶分區主內存的集成圖形:系統內存的一部分專門分配給GPU,零拷貝不可能實現,數據必須通過系統內存總線從一個分區復制到另一個分區。

    具有統一主存儲器的集成圖形,可在AMD Kaveri或PlayStation 4(HSA)中找到。

    16.2.5 PFIFO

    大多數引擎的命令都是通過一個名為PFIFO的特殊引擎發送的,PFIFO維護多個完全獨立的命令隊列,稱為通道(channel)FIFO,每個通道通過“通道控制區”進行控制,該區域是MMIO[GF100之前]或VRAM[GF100+]的區域,PFIFO攔截所有進入該區域的通道并對其采取行動。

    PFIFO內部在通道之間進行分時(time-sharing),但對應用程序是透明的,PFIFO控制的引擎也知道通道,并為每個通道維護單獨的上下文。

    PFIFO的上下文切換能力取決于卡的次代。在NV40,PFIFO基本上可以隨時在通道之間切換。在舊卡上,由于缺少緩存的后備存儲,只有在緩存為空時才能切換。然而,PFIFO控制的引擎在切換方面要差得多:它們只能在命令之間切換。雖然這種方式在舊卡上不是一個大問題(因為命令保證在有限的時間內執行),但引入具有循環功能的可編程著色器,可以通過啟動長時間運行的著色器來有效地掛起整個GPU。

    PFIFO大致可分為4個部分:

    • PFIFO pusher:收集用戶命令并將其注入。
    • PFIFO cache:等待執行的大量命令。
    • PFIFO puller:執行命令,將其傳遞給適當的引擎或驅動。
    • PFIFO switcher:標出通道的時間片,并在PFIFO寄存器和RAMFC內存之間保存/恢復通道的狀態。

    通道由以下部分組成:

    • 通道模式:PIO[NV1:GF100]、DMA[NV4:GF100]或IB[G80-]。
    • PFIFO DMA pusher狀態[僅限DMA和IB通道]。
    • PFIFO緩存狀態:已接受但尚未執行的命令。
    • PFIFO puller狀態。
    • RAMFC:當PFIFO上的通道當前未激活時,存儲上述信息的VRAM區域[用戶不可見]。
    • RAMHT[僅限GF100之前版本]:通道可以使用的“對象”表,對象由任意32位句柄標識,可以是DMA對象[參見NV3 DMA對象、NV4:G80 DMA對象、DMA對象]或引擎對象。在G80之前的卡上,可以在通道之間共享單個對象。
    • vspace(僅G80+):頁表的層次結構,描述引擎在執行通道命令時可見的虛擬內存空間,多個通道可以共享一個vspace。
    • 引擎特定狀態。

    通道模式決定向通道提交命令的方式。在GF100之前的卡上可以使用PIO模式,并且需要將這些方法直接插入通道控制區域。它的速度慢且脆弱——當多個通道同時使用時,很容易發生故障,故而不推薦使用。在NV1:NV40上,所有通道都支持PIO模式。在NV40:G80上,只有前32個通道支持PIO模式。在G80上:GF100僅通道0支持PIO模式。

    16.2.6 圖形卡剖析

    如今,圖形卡基本上是計算機中的計算機。它是一個復雜的野獸,在一個單獨的卡上有一個專用處理器,具有自己的計算單元、總線和內存。本節概述圖形卡,包括以下元素。

    • Graphics Memory(圖形內存)

    GPU的內存(視頻內存),可以是真實的、專用的、卡上內存(對于離散卡),也可以是與CPU共享的內存(對于集成卡,也稱為“被盜內存”或“雕刻內存”)。請注意,共享內存的情況有著有趣的含義,因為如果實現得當,系統到視頻內存的拷貝實際上是免費的。在專用內存的情況下,意味著需要進行來回傳輸,并且它們將受到總線速度的限制。

    現代GPU也有一種形式的虛擬內存,允許將不同的資源(系統內存的真實視頻內存)映射到GPU地址空間,與CPU的虛擬內存非常相似,但使用完全獨立的硬件實現。例如,較舊的Radeon卡(實際上是Rage 128)具有許多表面(Surface),我們可以將這些表面映射到GPU地址空間,每個表面都是連續的內存資源(視頻ram、AGP、PCI)。舊的Nvidia卡(NV40之前的所有卡)有一個類似的概念,它基于描述內存區域的對象,然后可以綁定到給定的用途。稍后的圖形卡(從NV50和R800開始)可以讓我們逐頁構建地址空間,還可以隨意選擇系統和專用視頻內存頁。這些與CPU虛擬地址空間的相似性非常驚人,事實上,未映射的頁面訪問可以通過中斷向系統發出信號,并在視頻內存頁面錯誤處理程序中執行。然而,小心處理這些問題,因為驅動程序開發人員必須處理來自CPU和GPU的多個地址空間,它們具有本質性的不同點。

    • Surface(表面)

    表面是所有渲染的基本源和目標。盡管它們的叫法有所差異(紋理、渲染目標、緩沖區…)基本思想總是一樣的。下圖描述了圖形表面的布局。由于硬件限制(通常是某些2次方的下一個倍數),表面寬度被四舍五入到我們所稱的間距,因此存在一個未使用的像素死區。

    圖形表面具有許多特征:

    • 表面的像素格式。像素顏色由其紅色、綠色和藍色分量以及用作混合不透明度的alpha分量表示。整個像素的位數通常與硬件大小相匹配(8、16或32位),但四個組件之間的位數重新分配不必與硬件大小相匹配。用于每個像素的位數被稱為每像素位數或bpp。常見的像素格式包括888 RGBX、8888 RGBA、565 RGB、5551 RGBA、4444 RGBA。請注意,現在大多數圖形卡都是在8888中原生工作的。
    • 寬度和高度是最明顯的特征,以像素為單位。
    • 間距是以字節為單位的寬度(不是以像素為單位!)表面的,包括空白區域(dead zone)像素。pitch便于計算內存使用量,例如,表面的大小應通過高度 x pitch而不是高度 x 寬度 x bpp來計算,以便包括dead zone。

    請注意,表面并非總是線性存儲在視頻內存中,事實上,出于性能原因,表面通常不是以線性存儲,因為這樣可以改善渲染時內存訪問的位置。此類表面稱為分塊表面(tiled surface),分塊表面的精確布局高度依賴于硬件,但通常是一種空間填充曲線,如Z曲線(又叫Z-order曲線、Morton曲線,下圖)或Hilbert曲線(下下圖)。

    另外,Morton和Hilbert曲線還支持3D空間的遍歷:

    • 2D Engine(2D引擎)

    2D引擎或blitter是用于2D加速的硬件,Blitter是最早的圖形加速形式之一,今天仍然非常普遍。通常,2D引擎能夠執行以下操作:

    • Blits。BLIT是GPU將內存矩形從一個位置復制到另一個位置的副本,源和目標可以是視頻或系統內存。
    • 實心填充。實心填充包括用顏色填充矩形內存區域,也可以包括alpha通道。
    • Alpha blits。Alpha Blit使用來自表面的像素的Alpha分量來實現透明度。
    • 拉伸拷貝。

    下圖顯示了在兩個不同表面之間拼接矩形的示例。此操作由以下參數定義:源和目標坐標、源和目標節距以及blit寬度和高度。然而,僅限于2D坐標,通常不能使用blitting引擎進行透視或變換。

    當blit發生在兩個重疊的源表面和目標表面之間時,副本的語義并不是簡單定義的,尤其是當人們認為blit發生的不是簡單的矩形移動,而是在核心逐像素移動時。如下圖所示,如果一行一行地從上到下復制,一些源像素將被修改為副作用。因此,拷貝方向的概念被引入到blitter中。在這種情況下,要獲得正確的副本,需要從下到上的副本。一些卡將根據表面重疊自動確定拷貝方向(例如nvidia GPU),而其他卡則不會,在這種情況下,必須由驅動處理。這就是為什么有些GPU實際上支持負的pitch,以便告訴2D引擎后退。

    最后,請記住,并非所有當前的圖形加速器都具有2D引擎。由于3D加速在技術上是2D加速的超集,因此可以使用3D引擎實現2D加速。事實上,一些驅動程序使用3D引擎來實現2D,使得GPU制造商可以完全放棄專用于2D的晶體管。然而,其他一些卡并不專用于晶體管,而是在GPU內部的3D操作之上對2D操作進行微程序化(nv10之后的nVidia卡和nv50之前的nVidia卡都是這種情況,對于Radeon R600系列,它們具有在3D之上實現2D的可選固件)。有時會影響2D和3D操作的混合,因為它們現在共享硬件單元。

    • 3D Engine(3D引擎)

    3D引擎也稱為光柵化引擎。它包含一系列以管線(單向)方式交換數據的階段,如頂點->幾何->片元、圖形FIFO、DMA等。為了獲得更好的緩存位置,紋理和表面通常會分塊。分塊意味著紋理不是線性存儲在GPU內存中,而是存儲在內存中,以便使紋理空間中接近的像素也在內存空間中接近,例如Z階曲線和希爾伯特曲線。

    • 覆蓋層和硬件精靈(Overlays and hardware sprites)。

    掃描輸出:圖形顯示的最后一個階段是將信息顯示在顯示設備或屏幕上,顯示設備是圖形鏈的最后一環,負責向用戶展示圖片。

    此外,還有數字與模擬信號、hsync、vsync、綠同步、連接器和編碼器(CRTC、TMD、LVDS、DVI-I、DVI-A、DVI-D、VGA)等技術,此文忽略之。

    16.2.7 圖形卡編程

    每個PCI卡公開多個PCI資源,lspci-v列出了這些資源,包含但不限于BIOSS、MMIO范圍、視頻存儲器(或僅部分)。由于PCI總資源大小有限,通常一張卡只能將其部分視頻內存作為資源公開,訪問剩余內存的唯一方法是通過其他可訪問區域的DMA(以類似于跳轉頁面的方式)。隨著視頻內存大小不斷增長,而PCI資源空間仍然有限,這種情況越來越普遍。

    • MMIO

    MMIO是卡的最直接訪問方式。一系列地址暴露給CPU,每個寫操作都直接進入GPU,使得從CPU到GPU的命令通信最簡單。這種編程是同步的;寫操作由CPU完成,并在GPU上以鎖步方式執行,會導致低于標準的性能,因為每次訪問都會在總線上變成一個數據包,而且CPU在提交后續命令之前必須等待以前的GPU命令完成。因此,MMIO僅用于當今驅動程序的非性能關鍵路徑

    • DMA

    直接內存訪問(DMA)是指外圍設備使用總線的總線主控功能,允許一個外設直接與另一個外設對話,而無需CPU的干預。在圖形卡的情況下,DMA最常見的兩種用途是:

    1、GPU與系統內存之間的傳輸(用于讀取紋理和寫入緩沖區)。允許在AGP或PCI上實現紋理,以及硬件加速的紋理傳輸。

    2、執行命令FIFO。由于CPU和GPU之間的MMIO是同步的,并且圖形驅動程序本身使用大量的I/O,因此需要更快的方式與卡通信。命令FIFO是圖形卡和CPU之間共享的一塊內存(系統內存或更罕見的視頻內存),CPU在其中放置命令供GPU稍后執行,然后GPU使用DMA異步讀取FIFO并執行命令。此模型允許異步執行CPU和GPU命令流,從而提高性能。

    • Interrupt(中斷)

    中斷通常是硬件外圍設備,尤其是GPU向CPU發送事件信號的一種方式。中斷的使用示例包括發出圖形命令完成的信號、發出垂直消隱事件的信號、報告GPU錯誤等等。

    當外圍設備引發中斷時,CPU會執行一個稱為中斷處理程序的小例程(routine),該例程會搶占其他當前執行。中斷處理程序有一個最長的執行時間,因此驅動程序必須保持較短的時間(不超過幾微秒)。為了執行更多的代碼,常見的解決方案是從中斷處理程序調度一個小任務(tasklet)。

    16.2.8 圖形硬件案例

    • 前向渲染

    前向渲染器(即經典渲染器)是渲染三維圖元最直接的方法,將向GPU逐個提交圖形API以繪制幾何體,并重復這個過程,是大多數移動GPU中使用的方法。

    與其他體系結構相比,NVidia硬件具有多種特殊性。第一個是多個上下文的可用性,它使用多個命令FIFO(類似于某些高端infiniband網卡的功能)和上下文切換機制來實現這些FIFO之間的轉換。一個小型固件用于上下文之間的上下文切換,負責將圖形卡狀態保存到內存的一部分并恢復另一個上下文。使用循環算法的調度系統處理上下文的選擇,并且時間片是可編程的。

    第二個特性是圖形對象的概念。Nvidia硬件具有兩個GPU訪問級別:第一個是原始級別,用于上下文切換;第二個是圖形對象,對原始級別進行微編程以實現高級功能(例如2D或3D加速)。

    • 延遲渲染

    延遲渲染器是GPU的不同設計。驅動程序將其存儲在內存中,而不是在渲染API提交時渲染每個3D圖元,當它注意到幀結束時,它會發出單個硬件調用來渲染整個場景。與經典體系結構相比,延遲渲染有許多優點:

    1、通過將屏幕拆分為分塊(通常在16 x 16到32 x 32像素范圍內),可以實現更好的渲染位置。然后,GPU可以迭代這些分塊,對于其中的每個分塊,可以在內部(迷你)zbuffer中解析每像素深度。渲染完整個分塊后,可以將其寫回視頻內存,從而節省寶貴的帶寬。類似地,由于可見性是在獲取紋理數據之前確定的,因此僅讀取有用的紋理數據(再次節省帶寬),并且僅對可見片元執行片元著色器(節省了計算能力)。

    2、如果不需要深度緩沖區值,則無需將其寫入內存。深度緩沖區分辨率可以在GPU內的每個分塊上實現,并且永遠不會寫回視頻內存,因此可以節省視頻內存帶寬和空間。

    當然,分塊渲染需要在開始繪制之前將整個場景存儲在內存中,并且還將增加延遲,因為我們甚至需要在開始繪制之前等待幀結束。當驅動程序已經允許應用程序提交下一幀的數據時,通過在GPU上繪制給定幀可以部分隱藏延遲問題。然而,在某些情況下(回讀、跨進程同步),并不總是能夠避免它。

    延遲渲染器對于帶寬通常非常稀缺的嵌入式平臺特別有用,并且應用程序非常簡單,因此額外的延遲和方法的限制無關緊要。SGX是延遲渲染GPU的一個示例。它使用分塊結構。SGX著色器結合了混合和深度測試。延遲渲染器的另一個示例是Mali系列GPU。

    總之,計算機中有多個內存域,它們不一致。GPU是一臺完全獨立的計算機,有自己的總線、地址空間和計算單元。CPU和GPU之間的通信是通過總線實現的,對性能有著非常重要的影響。GPU可以使用兩種模式進行編程:MMIO和命令FIFO。顯示設備沒有標準的輸出方法。


    16.3 操作系統圖形驅動

    16.3.1 Windows圖形驅動

    16.3.1.1 WDDM概述

    Windows圖形驅動程序由IHV(英特爾、NVIDIA、AMD、高通、PowerVR、VIA、Matrox等)實現并維護,是非常豐富的驅動程序。支持基本回退(基本渲染、基本顯示),實現最低限度的驅動功能,還支持虛擬化(VMware、Virtual Box、Parallels驅動程序)、遠程桌面方案(XenDesktop、RDP等)、虛擬顯示(intelligraphics、extramon等)。

    WDDM(Windows Display Driver Model)分為用戶模式內核模式,移動到用戶模式是為了穩定性和可靠性。

    用戶模式和內核模式組件之間的通信。

    vista之前的大部分藍屏都是由圖形驅動程序(來自MSDN):“在Windows XP中,大型復雜的顯示驅動程序可能是系統不穩定的主要原因。這些驅動程序完全在內核模式下執行(即深入系統代碼),因此,驅動程序中的一個問題通常會迫使整個系統重新啟動。根據在Windows XP時間段內收集的故障分析數據,顯示驅動程序占所有藍屏的20%。”

    在大多數進程中,用戶模式部分作為dll的一部分運行,仍然可以訪問表面(編碼器/解碼器、二進制植入、某些API可能部分(或間接)暴露于遠程訪問表面(例如WebGL)。下圖顯示了支持WDDM所需的體系結構。

    Windows的驅動模型演變歷史如下:

    • Windows XP:XPDM。

    • Windows Vista:WDDM 1.0、DWM和Aero。

      沒有DWM和有dWM的對比。因為DWM處理桌面的合成,所以當每個程序都必須跟蹤自己的顯示時,新功能就不可用了,任務欄中的Aero Peek,Aero Glass可在最上面的窗口后查看應用程序,能夠輕松改變方向,許多新的Windows動畫都依賴于DWM,包括“收縮到任務欄”效果。

    • Windows 7:WDDM 1.1,擴展DWM。

    • Windows 8:WDDM 1.2,刪除XPDM支持。

    • Windows 8.1:WDDM 1.3。

    • Windows 10:WDDM 2.0。

    16.3.1.2 WDDM架構

    早期的Windows版本(如Vista),圖形/游戲/多媒體/呈現堆棧架構如下:

    簡化后的架構圖如下:

    Windows Vista中的圖形核心如下:

    其中Windows Display Driver Model(WDDM,Windows顯示驅動模型)相關的模塊如下:

    WDDM是構建所有圖形的基石,基本原則是圖形功能正常,重新架構整個驅動程序堆棧,鞏固10年的發展,新的驅動程序模型,考量穩定性、安全、可用性(應用程序虛擬化)、性能。

    對于圖形API,早期的WDDM存在幾個版本,分別如下:




    對于Window窗口管理,架構如下:


    Windows圖形驅動程序模型適用于所有Windows用戶的單一圖形驅動程序模型,圖形驅動模型增強用戶體驗,創新實現了新的視覺和計算場景,性能和可靠性的改進使Windows能夠跨一系列形狀因素進行擴展。下面兩圖分布展示了Windows 7和8的對比:

    WDDM 1.2功能集:

    • 增強用戶體驗:立體3D體驗,平滑屏幕旋轉,無縫啟動和恢復,顯示“容器ID”支持。

      無縫引導、恢復和驅動程序升級。

      Windows 8上更快的睡眠和恢復-集成GPU。

    • 更好的性能:GPU優先權,睡眠和恢復優化,視頻內存提供和回收API、DDI,細粒度設備電源管理,SoC優化,基于分塊的渲染優化。

      改進的桌面和觸摸響應能力。快速流暢的地鐵風格和觸控體驗,支持細粒度GPU搶占,搶占越精細,響應速度越快。

      視頻內存提供和回收API。改進的視頻內存分配方案,優點:提高應用程序的視頻內存可用性,新D3D API和WDDM DDI
      :D3D應用程序、WDDM 1.2驅動程序。

      組件電源管理。

      直接翻轉。優化桌面組合以提高能效,在視頻幀播放期間節省內存拷貝。

      基于平鋪的渲染優化。TBR GPU的日益普及,針對TBR優化的圖形堆棧,節省電力,最小化內存帶寬使用率,分塊減少。

    • 改善可靠性:改進的GPU容錯能力,為開發人員和系統制造商提供更好的診斷,無需重新啟動服務器的驅動程序升級。

      改進的GPU容錯能力。左:windows 7的超時檢測和恢復改進,以前的操作系統,全局TDR,所有圖形應用程序重置并重新啟動。右:Windows 8的GPU掛起檢測,搶占超時,能夠執行長時間運行的任務,逐引擎的TDR。

    總之,Windows 8的WDDM實現視覺上豐富、快速和流暢的最終用戶體驗,通過各種形式因素帶來全新體驗,優化性能,同時節省電源,利用性能工具調整圖形驅動程序。

    如今,WDDM的驅動架構如下:

    以下是來自D3D的渲染操作流示例圖:


    下圖顯示了WDDM中顯示微型端口驅動程序的線程同步工作方式:

    16.3.1.3 WDDM接口

    kmd驅動是WDDM的內核模式的其中一個模塊,它看起來如下所示:

    NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
    {
        (...)
        DRIVER_INITIALIZATION_DATA DriverInitializationData;
        (...)
        DriverInitializationData.DxgkDdiEscape = DDIEscape;
        (...)
        Status = DxgkInitialize(DriverObject, RegistryPath, &DriverInitializationData);
        (...)
    }
    

    WDDM kmd驅動程序在同步時,為這些回調提供了一個線程模型,該模型基本上由四個級別組成(其中每個回調屬于其中一個級別):

    3:只有一個線程可以進入,GPU必須處于空閑狀態,沒有正在處理的DMA緩沖區,視頻內存被逐出到主機CPU內存。

    2:與3相同,但視頻內存移出除外。

    1:調用被分類為類,每個類只允許一個線程同時調用回調。

    0:完全可重入。

    如果允許并發,則兩個并發線程不能屬于同一進程,在尋找潛在的競爭條件場景時,需要謹記這一點。

    對于WDDM kmd驅動程序入口點,相當少的回調從userland獲得了重要的輸入:退出、渲染、分配、QueryAdapter,在找到它們之前,我們需要執行正確的驅動程序初始化,然后查看回調。涉及的結構體或接口:

    // Escape
    NTSTATUS D3DKMTEscape(_In_ const D3DKMT_ESCAPE *pData );
    typedef struct _D3DKMT_ESCAPE 
    {
        D3DKMT_HANDLE hAdapter;
        D3DKMT_HANDLE hDevice;
        D3DKMT_ESCAPETYPE Type;
        D3DDDI_ESCAPEFLAGS Flags;
        VOID *pPrivateDriverData;
        UINT PrivateDriverDataSize; 
        D3DKMT_HANDLE hContext;
    } D3DKMT_ESCAPE;
    
    // Render
    NTSTATUS APIENTRY DxgkDdiRender(_In_ const HANDLE hContext, _Inout_ DXGKARG_RENDER *pRender){ ... }
    typedef struct _DXGKARG_RENDER 
    {
        const VOID CONST *pCommand;
        const UINT CommandLength;
        VOID *pDmaBuffer;
        UINT DmaSize;
        VOID *pDmaBufferPrivateData;
        UINT DmaBufferPrivateDataSize;
        DXGK_ALLOCATIONLIST *pAllocationList;
        UINT AllocationListSize;
        D3DDDI_PATCHLOCATIONLIST *pPatchLocationListIn;
        UINT PatchLocationListInSize;
        D3DDDI_PATCHLOCATIONLIST *pPatchLocationListOut;
        UINT PatchLocationListOutSize;
        UINT MultipassOffset;
        UINT DmaBufferSegmentId;
        PHYSICAL_ADDRESS DmaBufferPhysicalAddress;
    } DXGKARG_RENDER;
    
    // Allocation
    NTSTATUS APIENTRY DxgkDdiCreateAllocation(const HANDLE hAdapter, DXGKARG_CREATEALLOCATION *pCreateAllocation){ ... }
    typedef struct
    _DXGKARG_CREATEALLOCATION 
    {
        const VOID *pPrivateDriverData;
        UINT PrivateDriverDataSize;
        UINT NumAllocations;
        DXGK_ALLOCATIONINFO *pAllocationInfo;
        HANDLE hResource;
        DXGK_CREATEALLOCATIONFLAGS Flags;
    } DXGKARG_CREATEALLOCATION;
    
    // queryadapter
    NTSTATUS APIENTRY DxgkDdiQueryAdapterInfo(HANDLE hAdapter, DXGKARG_QUERYADAPTERINFO *pQueryAdapterInfo ){ ... }
    typedef struct _DXGKARG_QUERYADAPTERINFO 
    {
        DXGK_QUERYADAPTERINFOTYPE Type; 
        VOID *pInputData;
        UINT InputDataSize;
        VOID *pOutputData;
        UINT OutputDataSize;
    } DXGKARG_QUERYADAPTERINFO;
    

    16.3.2 Linux圖形驅動

    Linux圖形堆棧在過去幾年中經歷了許多演變。本節的目的是詳細說明這段歷史,并給出多年來所做更改背后的理由。今天,設計仍然深深植根于這段歷史,本節將解釋這段歷史,以更好地推動Linux圖形堆棧的當前設計。下面簡述Linux圖形驅動架構涉及的各個模塊或概念:

    • PCI (Peripheral Component Interconnect):用戶需要將圖形卡插入主板上的PCI插槽。PCI規范不能免費向公眾提供,此計算機總線的編程入口點是PCI配置空間,是通過x86體系結構I/O端口地址空間中的0xCF8和0xCFC I/O端口訪問的。更具體地說,0xCF8是地址端口,0xCFC是數據端口。每個PCI實體(總線上的最小可尋址單元,如內存中的字節)有自己的配置空間。實體的配置空間中將有3種類型的資源:

      • 輸入/輸出內存。這是實體解碼的物理內存塊。簽出/proc/iomem的內容。
      • 輸入/輸出端口。將由實體解碼的I/O端口空間中的數據。簽出/proc/ioports文件的內容。
      • IRQ(中斷請求)。簽出/proc/irq目錄的內容。

      此類資源的配置使用2個系統完成:

      • PCI PNP(即插即用)已過時。
      • ACPI(高級配置和電源接口),目前實現的方法,是一項由OS(操作系統)內核完成的任務。
    • AGP (Accelerated Graphic Port)、PCI Express card:系統將看到插入AGP插槽或PCI Express插槽的圖形卡,就像PCI設備一樣。

    • ACPI (Advanced Configuration and Power Interface):如今的計算機通常具有ACPI功能,ACPI取代PCI PNP用于實體配置,并為該批次添加電源管理、多處理器sweet和其他內容。與PCI不同,ACPI規范是免費提供的。ACPI基于內存表:在計算機啟動時,操作系統必須在物理內存中找到RSDP(根系統描述指針)。在x86體系結構上,需要在特定的物理內存區域中查找“RSD PTR”字符串。

    • XORG、DDX (Device Dependent X)和DIX (Device Independent X):XORG是X Window系統客戶端libs和服務器的一部分、整個項目的參考實現,需要了解X Window核心協議。請記住,XORG有多個服務器,如DMX(分布式多頭X)服務器、kdrive服務器或著名的XGL服務器。DIX是XORG的一部分,負責處理客戶端、網絡透明度和軟件渲染。DDX是XORG處理硬件(以及在一定程度上處理操作系統)的一部分。

    • EXA:EXA是XORG加速的API,xfree86 DDX是唯一實現它的DDX。每次初始化屏幕時(例如,在新服務器生成開始時),都會初始化EXA加速(如果啟用)。

    • DRI (Direct Rendering Infrastructure) 和DRM (Direct Rendering Manager):DRI和DRM是圖形卡硬件編程的管道,主要由mesa(即“libre”opengl實現)使用,但由于對硬件的訪問必須在所有圖形卡硬件客戶端之間同步,xfree86 DDX視頻驅動模塊必須處理它,因為它本身就是這樣的客戶端。然后,EXA當想要執行加速操作時,必須與硬件進行DRI對話。有一個用于xfree86 DDX代碼的DRI xfree86 DDX模塊,希望通過DRI方式進行硬件訪問。此模塊和相關的xfree86 DDX代碼將使用DRM用戶級接口庫libdrm。理論上,未來的DRM演進將允許我們擺脫XORG服務器的PCI編程代碼。


    16.3.2.1 X11基礎架構

    X11架構圖如下,包含DIX(設備無關X)、DDX(設備相關X)、Xlib、套接字、X協議、X擴展,shm->用于傳輸的共享內存,XCB->異步等。

    X服務的內部交互圖如下:

    16.3.2.2 DRI/DRM基礎架構

    直接渲染管理器(DRM)是Linux內核的一個子系統,負責與現代視頻卡的GPU接口。DRM公開了一個API,用戶空間程序可以使用該API向GPU發送命令和數據,并執行諸如配置顯示器的模式設置等操作。DRM最初是作為X Server直接渲染基礎設施的核心空間組件開發的,但從那時起,它已被其他圖形堆棧替代品(如Wayland)使用。

    DRM允許多個程序同時訪問3D視頻卡,避免沖突。

    使用Linux內核的直接渲染管理器訪問3D加速圖形卡的過程。

    直接渲染管理器體系結構詳細信息:DRM核心和DRM驅動程序(包括GEM和KMS),由libdrm接口。

    最初(當Linux首次支持圖形硬件加速時),只有一段代碼可以直接訪問圖形卡:XFree86服務器。設計如下:通過以超級用戶權限運行,XFree86服務器可以從用戶空間訪問卡,并且不需要內核支持來實現2D加速。這種設計的優點是簡單,而且XFree86服務器可以很容易地從一個操作系統移植到另一個操作系統,因為它不需要內核組件。多年來,這是最廣泛的X服務器設計(盡管也有明顯的例外,比如XSun,它在內核中為一些驅動程序實現了modesetting)。

    后來,第一個獨立于硬件的3D加速設計Utah-GLX出現在Linux上,Utah-GLX基本上包含一個實現GLX的附加用戶空間3D驅動程序,并以類似于2D驅動程序的方式從用戶空間直接訪問圖形硬件。在3D硬件與2D明顯分離的時代(因為2D和3D使用的功能完全不同,或者因為3D卡是一個完全獨立的卡,即3Dfx),擁有一個完全獨立的驅動程序是有意義的。此外,從用戶空間直接訪問硬件是在Linux下實現3D加速的最簡單方法和最短途徑。

    與此同時,幀緩沖區驅動程序越來越廣泛,它代表了可以同時直接訪問圖形硬件的另一個組件。為了避免幀緩沖區和XFree86驅動程序之間的潛在沖突,決定在VT交換機上,內核將向X服務器發出信號,告訴其保存圖形硬件狀態。要求每個驅動程序在VT交換機上保存其完整的GPU狀態使驅動程序更加脆弱,對于突然面臨不同驅動程序之間容易出現錯誤的交互的開發人員來說,生活變得更加困難。請記住,XFree86驅動程序至少有兩種可能(xf86視頻vesa和本機XFree86驅動程序)和兩種內核幀緩沖區驅動程序(vesafb和本機幀緩沖區驅動程序),因此每個GPU至少有四種共存驅動程序的組合。

    顯然,這種模式有缺點。首先,它要求允許未經授權的用戶空間應用程序訪問3D圖形硬件。其次,如上圖所示,所有GL加速都必須通過X協議間接進行,將顯著降低其速度,尤其是對于紋理上傳等數據密集型功能。由于人們越來越擔心Linux的安全性和性能缺陷,需要另一種模型。

    為了解決Utah-GLX模型的可靠性和安全性問題,將DRI模型放在一起;XFree86及其后續版本X.Org都使用了它。該模型依賴于一個額外的內核組件,其職責是從安全角度檢查3D命令流的正確性。現在的主要變化是,沒有權限的OpenGL應用程序將向內核提交命令緩沖區,內核將檢查它們的安全性,然后將它們傳遞給硬件執行,而不是直接訪問卡。這種模型的優點是不再需要信任用戶空間,但XFree86的2D命令流仍然沒有通過DRM,因此X服務器仍然需要超級用戶權限才能直接映射GPU寄存器。

    當前堆棧是從一組新的需求演變而來的。首先,要求X服務器具有超級用戶權限總是會帶來嚴重的安全隱患。其次,在以前的設計中,不同的驅動程序接觸單個硬件,通常會導致問題。為了解決這個問題,關鍵有兩個方面:第一,將內核幀緩沖區功能合并到DRM模塊中,第二,讓X.Org通過DRM模塊訪問圖形卡并無權限運行。這種模型被稱為內核模式設置(KMS),在這個模型中,DRM模塊現在負責作為幀緩沖區驅動程序和X.Org提供模式設置服務。

    總之,應用程序通過封裝繪圖調用的特定庫與X.Org通信,當前的DRI設計隨著時間的推移在許多重要步驟中不斷發展,在現代堆棧中,所有圖形硬件活動都由內核模塊DRM控制。Linux總的模塊交互圖如下:

    更詳細的交互和分層架構如下:

    16.3.2.3 Framebuffer驅動

    在核心,幀緩沖區驅動程序實現以下功能:

    • 模式設置。模式設置包括配置視頻模式以在屏幕上獲取圖片,包括選擇視頻分辨率和刷新率。
    • 可選2d加速。幀緩沖區驅動程序可以提供用于加速linux控制臺的基本2D加速,包括視頻內存中的拷貝和實體填充。加速有時通過掛鉤提供給用戶空間(然后用戶空間必須對特定于卡的MMIO寄存器進行編程,需要root權限)。

    通過只實現這兩部分,幀緩沖區驅動程序仍然是最簡單、最合適的linux圖形驅動程序形式。幀緩沖區驅動程序并不總是依賴于特定的卡型號(如nvidia或ATI)。存在vesa、EFI或Openfirmware之上的驅動程序,這些驅動程序不是直接訪問圖形硬件,而是調用固件功能來實現模式設置和2D加速。

    幀緩沖區驅動程序是linux圖形驅動程序的最簡單形式。只需很少的實現工作,幀緩沖區驅動程序提供了較低的內存占用,因此對嵌入式設備很有用。實現加速是可選的,因為存在軟件回退功能。

    16.3.2.4 直接渲染管理器

    在復雜的世界中,使用內核模塊是一項要求。此內核模塊稱為直接渲染管理器(DRM),可用于多種用途:

    • 將圖形卡的關鍵初始化放在內核中,例如上載固件或設置DMA區域。
    • 在多個用戶空間組件之間共享渲染硬件,并處理訪問。
    • 通過防止應用程序對任意內存區域執行DMA,以及更一般地防止以任何可能導致安全漏洞的方式對卡進行編程來加強安全性。
    • 通過向用戶空間提供視頻內存分配功能來管理卡的內存。
    • DRM得到了改進,以實現模式設置,簡化了以前DRM和幀緩沖區驅動程序爭用同一GPU的情況。相反,將刪除幀緩沖區驅動程序,并在DRM中實現幀緩沖區支持。

    內核模塊(DRM)是全局DRI/DRM用戶空間/內核方案(下圖包含libdrm-DRM-入口點-多個用戶空間應用程序):

    當設計一個Linux圖形驅動程序以實現不僅僅是簡單的幀緩沖區支持時,首先要做的是一個DRM組件,應該派生出一種既高效又能加強安全性的設計。DRI/DRM方案可以以不同的方式實現,并且接口確實完全是特定于卡的。

    DRM批量緩沖區提交模型:DRM設計的核心是DRM_GEM_EXECBUFFER ioctl,允許用戶空間應用程序向內核提交一個批處理緩沖區,然后內核將其放在GPU上。此ioctl允許許多事情,如共享硬件、管理內存和強制執行內存保護。

    DRM的職責之一是在多個用戶空間進程之間復用GPU本身。如果GPU保持圖形狀態,那么當多個應用程序使用同一GPU時,就會出現一個問題:如果什么都不做,應用程序就會破壞彼此的狀態。根據當前的硬件,主要有兩種情況:

    1、當GPU具有硬件狀態跟蹤功能時,硬件共享會更簡單,因為每個應用程序都可以發送到單獨的上下文,GPU會跟蹤每個應用程序本身的狀態。此種方法就是新驅動的工作方式。

    2、當GPU沒有多個硬件上下文時,復用硬件的常見方法是在每個批處理緩沖區的請求時重新提交狀態,是intel和radeon驅動程序多路復用GPU的方式。請注意,重新提交狀態的職責完全依賴于用戶空間。如果用戶空間沒有在每個批處理緩沖區開始時重新提交狀態,那么來自其他DRM進程的狀態將泄漏到它身上。DRM還防止同時訪問同一硬件。

    內核能夠移動內存區域來處理內存壓力大的情況。根據硬件的不同,有兩種實現方法:

    1、如果硬件具有完整的內存保護和虛擬化,則可以在分配內存資源時將其分頁到GPU中,并隔離每個進程。因此,支持GPU內存的內存保護不需要太多。

    2、當硬件沒有內存保護時,仍然可以完全在內核中實現,用戶空間完全不受其影響。為了允許重新定位對用戶空間進程起作用,而用戶空間進程在其他方面并不知道它們,命令提交ioctl將通過將所有硬件偏移量替換到當前位置來重寫內核中的命令緩沖區。由于內核知道所有內存緩沖區的當前位置,使得前述方法成為可能。為了防止訪問任意GPU內存,命令提交ioctl還可以檢查這些偏移量中的每一個是否為調用進程所有,如果不是,則拒絕批處理緩沖區。這樣,當硬件不提供該功能時,就可以實現內存保護。

    DRM管理現代linux圖形堆棧中的所有圖形活動,是堆棧中唯一受信任的部分,負責安全,因此,不應信任其他組件。它提供基本的圖形功能:模式設置、幀緩沖區驅動程序、內存管理。

    16.3.2.5 Mesa

    Mesa也稱為Mesa3D和Mesa3D圖形庫,是OpenGL、Vulkan和其他圖形API規范的開源軟件實現。Mesa將這些規范轉換為特定于供應商的圖形硬件驅動程序。其最重要的用戶是兩個圖形驅動程序,它們主要由Intel和AMD為各自的硬件開發和資助(AMD在不推薦的AMD Catalyst上推廣其Mesa驅動程序Radeon和RadeonSI,Intel只支持Mesa驅動程序)。專有圖形驅動程序(如Nvidia GeForce驅動程序和Catalyst)取代了所有Mesa,提供了自己的圖形API實現,開發名為Nouveau的Mesa Nvidia驅動程序的開源工作主要由社區開發。

    除了游戲等3D應用程序外,現代顯示服務(X.org的Glamer或Wayland的Weston)也使用OpenGL/EGL;因此,所有圖形通常都要使用Mesa。Mesa由freedesktop托管,該項目于1993年8月由布萊恩·保羅發起。Mesa隨后被廣泛采用,現在包含了世界各地各種個人和公司的眾多貢獻,包括管理OpenGL規范的Khronos集團圖形硬件制造商的貢獻。

    Mesa有兩個主要用途:

    1、Mesa是OpenGL的軟件實現,被認為是參考實現,在檢查一致性時很有用,因為官方的OpenGL一致性測試并不公開。
    2、Mesa為linux下的開源圖形驅動程序提供了OpenGL入口點。

    Mesa是Linux下的參考OpenGL實現,所有開源圖形驅動程序都使用Mesa for 3D。

    視頻游戲通過OpenGL將渲染計算實時外包給GPU,著色器使用OpenGL著色語言或SPIR-V編寫,并在CPU上編譯,編譯后的程序在GPU上執行。(下圖)

    Linux圖形堆棧見下圖:DRM&libDRM,Mesa 3D,其中顯示服務屬于窗口系統,只用于游戲等上層應用。

    Wayland的免費實現依賴于EGL的Mesa實現,名為libwayland EGL的特殊庫是為支持對幀緩沖區的訪問而編寫的,EGL 1.5版本已過時。在GDC 2014上,AMD正在探索使用DRM而不是其內核內blob的戰略變化。

    16.3.2.6 Wayland

    Wayland旨在更簡單地替代X,更易于開發和維護。Wayland是一種用于合成器與其客戶機對話的協議,也是該協議的C庫實現。合成器可以是在Linux內核模式設置和evdev輸入設備上運行的獨立顯示服務器、X應用程序或wayland客戶端本身。客戶端可以是傳統應用程序、X服務器(無根或全屏)或其他顯示服務器。Wayland項目的一部分也是Wayland合成器的Weston參考實現,Weston可以作為X客戶端或Linux KMS運行,Weston合成器是一種最小且快速的合成器,適用于許多嵌入式和移動用例。

    使用X(左)和Wayland(右)的驅動架構對比。

    16.3.3 調度機制

    16.3.3.1 OS And GPU abstraction

    早在10年前,微軟聯合大學的科研人員在Operating Systems must support GPU abstractions中指出,缺乏對GPU抽象的操作系統支持從根本上限制了GPU在許多應用領域的可用性。操作系統為最常見的資源(如CPU、輸入設備和文件系統)提供抽象。相比之下,操作系統目前將GPU隱藏在笨拙的ioctl 接口后面,將抽象的負擔轉移到用戶庫和運行時上。因此,操作系統無法為GPU提供系統范圍的保證,例如公平性和隔離性,開發人員在構建集成GPU和其他操作系統管理資源的系統時,必須犧牲模塊化和性能。他們提出了新的內核抽象,以支持GPU和其他加速器設備作為一流的計算資源。

    CPU與GPU程序的技術堆棧。對于GPU程序,CPU程序的操作系統級別和用戶模式運行時抽象之間沒有1對1的對應關系。

    不存在對GPU抽象的直接操作系統支持,因此利用GPU完成此工作負載必然需要一個用戶級GPU編程框架和運行時,如CUDA或OpenCL。在這些框架中實現xform和detect會大大提高隔離運行的組件的速度,但組合系統(catusb | xform | detect | hidinput)會因跨用戶內核邊界和跨PCI-e總線的硬件的過度數據移動而受損。

    現代操作系統目前無法保證GPU的公平性和性能隔離,主要是因為GPU不是作為共享計算資源(如CPU)管理的,而是作為I/O設備管理的,其接口僅限于一小部分已知操作(如init_module、read、write、ioctl)。當操作系統需要使用GPU來實現其功能時,這種設計就成為了一個嚴重的限制。實際上,NVIDIA GPU Direct實現了這樣一個功能,但需要在所涉及的任何I/O設備的驅動程序中提供專門的支持。自己計算(例如,Windows 7與Aero用戶界面一樣)。在當前的制度下,時間分割和超時可以確保屏幕刷新率保持不變,但在執行公平性和系統負載平衡時,操作系統在很大程度上取決于GPU驅動程序。

    新的體系結構可能會改變跨GPU和CPU內存域管理數據的相對難度,但軟件將繼續發揮重要作用,在可預見的未來,優化數據移動仍然很重要。AMD的Fusion將CPU和GPU集成到一個芯片上,然而,它將CPU和GPU內存分區。Intel的Sandy Bridge(另一種CPU/GPU組合)表明,未來幾年將出現各種形式的集成CPU/GPU硬件上市。新的混合系統,例如NVIDIA Optimus,在芯片和高性能離散圖形卡上都具有能效,即使使用組合的CPU/GPU芯片,數據管理也更加明確。但即使是一個完全集成的虛擬內存系統,也需要系統支持來最小化數據拷貝。

    原型系統中基于CUDA的xform程序實現的相對GPU執行時間和開銷(越低越好)。sync使用CPU和GPU之間的緩沖區同步通信,async使用異步通信,async pp同時使用異步和乒乓緩沖區來進一步隱藏延遲。條形圖分為在GPU上執行的時間和系統開銷。DtoH表示設備和主機之間在每個幀上進行通信的實現,反之亦然,并且兩者都表示每個幀的雙向通信。報告的執行時間與同步、雙向情況(同步兩者)相關。

    CPU密集工作對GPU密集任務的影響。當系統中存在并行GPU和CPU工作時,當前的操作系統抽象限制了操作系統提供性能隔離的能力。H→D是一個CUDA工作負載,它具有從主機到GPU設備的通信,而H←D具有從GPU到主機的通信,H?D具有雙向通信。

    g)

    GPU密集工作對CPU密集任務的影響。這些圖顯示了操作系統能夠在程序大量使用GPU的60秒內傳遞鼠標移動事件的頻率(以Hz為單位)。在此期間的平均CPU利用率低于25%。

    他們提出了以下新的操作系統抽象,可以使GPU適用于更廣泛的應用程序域。這些抽象允許將計算表示為有向圖,從而實現高效的數據移動和高效、公平的調度。

    • PTask。PTask類似于傳統的OS進程抽象,但PTask基本上在GPU上運行。PTask需要來自OS的一些編排來協調其執行,但不需要用戶模式主機進程。PTask有一個可以綁定到端口的輸入和輸出資源列表(類似于POSIX stdin、stdout、stderr文件描述符)。

    • Port。Port是內核命名空間中的一個對象,可以綁定到PTask輸入和輸出資源。一個Port是一個數據源或接收器,提供了一種方法來公開GPU代碼中的數據和參數,這些數據和參數必須動態綁定,并且可以由GPU或CPU內存中的緩沖區填充。

    • Channel。Channel類似于POSIX管道:它將端口連接到其他端口,或連接到系統中的其他數據源和接收器,如I/O總線、文件等。Channel具有子類型GraphInputChannel、GraphOutChannel和GraphInternalChannel。

    • Graph。Graph是PTask節點的集合,其輸入和輸出端口通過通道連接。可以獨立創建和執行多個圖,PTask運行時負責公平地調度它們。

    在操作系統接口上支持這些新的抽象需要新的系統調用來創建和管理ptask、端口和通道,額外的系統調用類似于POSIX中的進程API、進程間通信API和調度器提示API。

    與GPU協調操作系統調度的兩個主要好處是:

    • 效率性。是指ptask準備就緒和在GPU上調度之間的低延遲,以及在GPU上調度足夠的ptask工作以充分利用其計算帶寬。
    • 公平性。是指操作系統調度器在GPU利用率和用戶界面響應性之間取得平衡。此外,與GPU競爭的PTAK都能獲得其計算帶寬的合理份額。并非所有PTASK都有所有這些調度要求,如典型的CUDA程序不關心低延遲調度。

    總之,他們主張對內核抽象進行根本性的重組,以管理交互式、大規模并行設備。內核必須根據需要只公開足夠的硬件細節,以使程序員能夠實現良好的性能和低延遲,同時提供根據機器拓撲進行封裝和專門化的通信抽象。GPU是一種通用的共享計算資源,必須由操作系統進行管理,以提供公平性和隔離性。

    16.3.3.2 Halide Pipeline

    Schedule Synthesis for Halide Pipelines on GPUs揭示了Halide DSL和編譯器通過分離算法描述和優化調度,實現了針對異構體系結構的圖像處理管道的高性能代碼生成。然而,自動調度生成目前僅適用于多核CPU體系結構。因此,在為具有GPU功能的平臺進行優化時,仍然需要專家級知識。他們使用新的優化過程擴展了當前的Halide自動調度程序,以高效地生成基于CUDA的GPU體系結構的調度。實驗結果表明,該調度平均比手動調度快10%,比以前的自動調度快2倍以上。

    通用管道示例:在將管道拆分為分配了優化計劃的較小階段組之前,將普通階段內聯到其使用者中。

    簡單的實現會在不同的CUDA內核中完全計算每個階段,并將所有數據存儲到全局內存中。

    重疊分塊時間表計算一次分塊內迭代(或線程塊)所需的所有像素,并將其存儲在共享內存中。

    該文還介紹了在Halide master自動調度程序中實現的新優化過程,以生成針對基于CUDA的GPU架構的優化調度。其遵循一個類似于當前優化流程的過程,其中瑣碎(逐點消耗)階段首先被內聯到其消費者中,然后使用Halide master中實現的貪心算法進行分組。下圖顯示了autoscheduler使用的優化流的概述。

    基本調度流程:調度器需要循環邊界估計以及用戶給定的目標規范描述,以生成給定管道的優化調度。編譯流中的大多數步驟都已擴展,以支持自動GPU調度。

    16.3.3.3 Hardware Accelerated GPU Scheduling

    Hardware Accelerated GPU Scheduling闡述了WDDM在2020年5月引入的一種基于硬件加速的GPU調度機制。

    自從Windows顯示驅動程序Model 1.0(WDDM)的推出以及GPU調度在Windows中的引入,已經過去了將近14年。很少有人會記得WDDM之前的日子,在那里,應用程序只需向GPU提交他們想要的工作即可。他們提交到一個全局隊列,在那里它以嚴格的“先提交,先執行”方式執行。在大多數GPU應用程序都是全屏游戲的時候,這些最基本的調度方案是可行的,一次運行一個。

    隨著向使用GPU實現更豐富圖形和動畫的廣泛應用程序的過渡,該平臺需要更好地確定GPU工作的優先級,以確保響應的用戶體驗。因此,WDDM GPU調度程序誕生了。

    隨著時間的推移,Windows顯著增強了WDDM核心的GPU調度程序,支持每個新WDDM版本的其他功能和場景。然而,在整個發展過程中,調度器的一個方面沒有改變。他們一直在CPU上運行一個高優先級線程,該線程負責協調、優先排序和安排各種應用程序提交的工作。

    這種調度GPU的方法在提交開銷以及工作到達GPU的延遲方面有一些基本的限制,這些開銷大部分被傳統的應用程序編寫方式所掩蓋。例如,應用程序通常會在第N幀上執行GPU工作,并讓CPU提前運行,為第N+1幀準備GPU命令。這種GPU命令的批量緩沖允許應用程序每幀只提交幾次,從而最大限度地降低調度成本,并確保良好的CPU-GPU執行并行性。

    CPU和GPU之間緩沖的一個固有副作用是,用戶體驗到的延遲會增加。CPU在“第N+1幀”期間拾取用戶輸入,但GPU直到下一幀才渲染用戶輸入,延遲減少和提交/調度開銷之間存在根本的緊張關系。應用程序可以更頻繁地提交,以小批量方式提交以減少延遲,或者可以提交更大批量的工作以減少提交和調度開銷。

    隨著Windows 2020年5月10日的更新,其引入一個新的GPU調度程序作為用戶選擇加入,但默認關閉選項。有了正確的硬件和驅動程序,Windows現在可以將大部分GPU調度卸載到基于GPU的專用調度處理器上。Windows系統可以通過Windows設置 -> 系統 -> 顯示 -> 圖形設置訪問設置頁面,以便開啟或關閉硬件加速的GPU調度。

    Windows繼續控制優先級,并決定哪些應用程序在上下文中具有優先級。他們將高頻任務卸載到GPU調度處理器,處理各種GPU引擎的量子管理和上下文切換。新的GPU調度程序是對驅動程序模型的重大和根本性更改,更改調度程序類似于在仍居住在房屋中的情況下重建房屋的基礎。

    雖然新的調度器減少了GPU調度的開銷,但大多數應用程序都被設計為通過緩沖來隱藏調度成本。硬件加速GPU調度第一階段的目標是使圖形子系統的一個基本支柱現代化,并為將來的事情做好準備。

    就目前而言,有評測文章發現,開啟此項功能對部分3A游戲并無實際的幀率提升。

    16.3.3.4 TimeGraph

    GPU現在常用于圖形和數據并行計算。隨著越來越多的應用程序趨向于在多任務環境中的GPU上加速,其中多個任務同時訪問GPU,操作系統必須在GPU資源管理中提供優先級和隔離功能,特別是在實時設置中。當前主流的GPU是命令驅動的:

    多任務的常見問題是CPU常常會導致GPU阻塞:

    TimeGraph: GPU Scheduling for Real-Time Multi-Tasking Environments介紹了TimeGraph——一種設備驅動程序級別的實時GPU調度器,用于保護重要的GPU工作負載免受性能干擾。

    TimeGraph采用了一種新的事件驅動模型,該模型將GPU與CPU同步,以監控從用戶空間發出的GPU命令,并以響應方式控制GPU資源的使用。TimeGraph支持兩種基于優先級的調度策略,以解決GPU處理的異步性和非搶占性帶來的響應時間和吞吐量之間的權衡問題。資源保留機制還用于說明和強制執行GPU資源使用,從而防止行為不端的任務耗盡GPU資源。進一步提供GPU命令執行成本的預測,以增強隔離。

    他們使用OpenGL圖形基準進行的實驗表明,即使面對極端的GPU工作負載,TimeGraph也能將主要GPU任務的幀速率保持在所需的水平,而如果沒有TimeGraph支持,這些任務幾乎沒有響應。此外還發現,施加在時間圖上的性能開銷可以限制在4-10%,其事件驅動調度器的吞吐量比現有的tick驅動調度器提高了約30倍。

    該文假設系統由通用多核CPU和板載GPU組成,不操縱任何GPU內部單元,因此GPU命令在提交到GPU后不會被搶占。TimeGraph獨立于庫、編譯器和運行時引擎,因此,時間圖的原理適用于不同的GPU架構(如NVIDIA Fermi/Tesla和ATI Stream)和編程框架(如OpenGL、OpenCL、CUDA和HMPP)。目前,TimeGraph是為Gallium3D OpenGL軟件棧中的Nouveau設計和實現的,該軟件棧也計劃支持OpenCL。此外,TimeGraph已移植到打包在PathScale ENZO套件中的PSCNV開源驅動程序,該套件支持CUDA和HMPP。然而,鑒于目前可用的一組開源解決方案:Nouveau和Gallium3D,該文主要關注OpenGL工作負載。

    TimeGraph是設備驅動程序的一部分,它是用戶空間程序向GPU提交GPU命令的接口。假設設備驅動程序是基于大多數類UNIX操作系統中采用的直接渲染基礎設施(DRI)模型設計的,作為X Window系統的一部分。在DRI模式下,用戶空間程序可以直接訪問GPU來渲染幀,而無需使用窗口協議,同時仍可以使用窗口服務器將渲染幀blit到屏幕上。GPGPU框架不需要這樣的窗口過程,因此它們的模型更加簡化。

    為了向GPU提交GPU命令,必須為用戶空間程序分配GPU通道,這些通道在概念上表示GPU上的單獨地址空間。例如,NVIDIA Fermi和Tesla架構支持128個通道,每個通道的GPU命令提交模型如下圖所示。

    GPU命令提交模型。

    每個通道使用兩種類型的內核空間緩沖區:用戶推送緩沖區和內核推送緩沖區。用戶推送緩沖區映射到相應任務的地址空間,其中GPU命令從用戶空間推送。GPU命令通常分組為非搶占區域,以匹配用戶空間原子性假設。同時,內核推送緩沖區用于內核原語,如主機設備同步、GPU初始化和GPU模式設置。

    當用戶空間程序將GPU命令推送到用戶推送緩沖區時,它們還將數據包寫入內核推送緩沖區的特定環形緩沖區部分,稱為間接緩沖區,每個數據包都是一個(大小和地址)元組,用于定位某個GPU命令組。驅動程序將GPU上的命令調度單元配置為讀取用于命令提交的緩沖區,這個環形緩沖區由GET和PUT指針控制,指針從同一個地方開始。每次數據包寫入緩沖區時,驅動程序都會將PUT指針移動到數據包的尾部,并向GPU命令調度單元發送信號,以下載位于GET和PUT指針之間的數據包所在的GPU命令組。然后,GET指針會自動更新到與PUT指針相同的位置。一旦這些GPU命令組提交到GPU,驅動程序將不再管理它們,并繼續提交下一組GPU命令組(如果有)。因此,這個間接緩沖區扮演著命令隊列的角色。

    每個GPU命令組可以包括多個GPU命令,每個GPU命令都由標頭(header)和數據組成,標頭包含方法和數據大小,而數據包含傳遞給方法的值。方法表示GPU指令,其中一些指令在計算和圖形之間共享,另一些則針對每種指令。我們假設,一旦GPU命令組卸載到GPU上,設備驅動程序不會搶占它們。在同一GPU通道中,GPU命令執行順序錯誤。GPU通道由GPU引擎自動切換。

    上述驅動程序模型基于直接渲染管理器(DRM),尤其針對NVIDIA Fermi和Tesla架構,但也可以用于其他架構,只需稍加修改。

    TimeGraph的體系結構及其與軟件堆棧其余部分的交互如下圖所示。用戶空間程序無需修改,GPU命令組可以通過現有軟件框架生成。然而,TimeGraph需要與設備驅動程序空間中名為PushBuf的特定接口進行通信,PushBuf接口允許用戶空間提交存儲在用戶推送緩沖區中的GPU命令組。TimeGraph使用此PushBuf接口將GPU命令組排隊,它還使用為GPU到CPU中斷準備的IRQ處理程序來調度下一個可用的GPU命令組。

    TimeGraph由GPU命令調度器、GPU保留管理器和GPU命令探查器組成。GPU命令調度器根據任務優先級對GPU命令組進行排隊和調度,它還與GPU保留管理器協調,以計算和強制執行任務的GPU執行時間。GPU命令探查器支持預測GPU命令執行成本,以避免超出保留范圍。支持兩種調度策略來解決響應時間和吞吐量之間的權衡問題:

    • 可預測響應時間(PRT):此策略最大限度地減少GPU上的優先級反轉,以根據優先級提供可預測的響應時間。當GPU不空閑時,GPU命令排隊;當GPU空閑時,GPU命令被調度:

    • 高吞吐量(HT):此策略增加總吞吐量,允許額外的優先級反轉。當GPU不空閑時,只有當優先級低于當前GPU上下文時,GPU命令才會排隊;當GPU空閑時,會調度GPU命令:

      ng)

    它還支持兩種GPU保留策略,以解決隔離和吞吐量之間的權衡問題:

    • 后驗強制(PE):此策略在GPU命令組完成后強制GPU資源使用,而不犧牲吞吐量。優化GPU資源使用,指定每個任務的容量(C)和周期(P)(/proc/GPU/$任務):

    • 先驗強制(AE):此策略在提交GPU命令組之前,使用GPU執行成本預測強制GPU資源使用,但會增加額外的開銷。指定每個任務的容量(C)和周期(P)(/proc/GPU/$任務):

    為了將多個任務統一到一個保留中,TimeGraph保留機制提供了共享保留模式。特別是,TimeGraph在加載時使用PE策略創建一個特殊的共享保留實例(稱為Background,它為不屬于任何特定保留的所有GPUAccessed任務提供服務)。下圖顯示了PushBuf接口和IRQ處理程序的高級圖,其中TimeGraph引入的修改以粗體框架突出顯示。此圖基于Nouveau實現,但大多數GPU驅動程序應該具有類似的控制流。

    PushBuf接口和IRQ處理程序與時間圖方案的關系圖。

    GPU命令調度器的目標是根據任務優先級對非搶占式GPU命令組進行排隊和調度。為此,TimeGraph包含一個暫停任務的等待隊列,它還管理GPU聯機列表,即指向當前在GPU上執行的GPU命令組的指針列表。

    當GPU命令組進入PushBuf界面時,GPU聯機列表用于檢查當前是否有正在執行的GPU命令組。如果列表為空,則將相應的任務插入其中,并將GPU命令組提交給GPU。否則,任務將插入到要調度的等待隊列中。

    GPU聯機列表的管理需要有關GPU命令組何時完成的信息。TimeGraph采用事件驅動模型,該模型使用GPU到CPU中斷來通知每個GPU命令組的完成,而不是以前工作中采用的tick驅動模型。每次中斷時,相應的GPU命令組將從GPU聯機列表中刪除。

    TimeGraph支持兩種GPU調度策略。可預測響應時間(PRT)策略鼓勵此類任務在不影響重要任務的情況下及時執行。此策略在某種意義上是可預測的,GPU命令組是基于任務優先級進行調度的,以使高優先級任務在GPU上響應。另一方面,高吞吐量(HT)策略適用于應該盡可能快地執行的任務。有一個權衡,即PRT策略以犧牲吞吐量為代價防止任務受到干擾,而HT策略實現了一個任務的高吞吐量,但可能會阻止其他任務。例如,桌面小部件、瀏覽器插件和視頻播放器任務需要使用PRT策略,而三維游戲和交互式三維接口任務可以使用HT策略。

    PRT策略強制任何GPU命令組等待前面的GPU命令組(如果有)完成。具體而言,如果GPU聯機列表為空,則到達設備驅動程序的新GPU命令組可以立即提交給GPU。否則,相應的任務必須在等待隊列中休眠。等待隊列中的最高優先級任務(如果有)在GPU的每次中斷時被喚醒。

    下圖(a)顯示了在PRT策略下如何在GPU上調度具有不同優先級的三個任務,即高優先級、中優先級(MP)和低優先級(LP)。當MP任務到達時,其GPU命令組可以在GPU上執行,因為沒有GPU命令組正在執行。如果GPU和CPU異步運行,則MP任務可以在其上一個GPU命令組執行時再次到達。但是,根據PRT策略,由于GPU沒有空閑,MP任務這次將排隊。由于同樣的原因,甚至下一個HP任務也會排隊,因為更高優先級的任務可能很快就會到達。TimeGraph附加在每個GPU命令組末尾的特定GPU命令集會向CPU生成一個中斷,并相應地調用TimeGraph調度程序以喚醒等待隊列中的最高優先級任務。因此,接下來選擇在GPU上執行HP任務,而不是MP任務。這樣,LP任務的下一個實例和HP任務的第二個實例將根據其優先級進行調度。

    鑒于GPU命令組的到達時間未知,且每個GPU命令組都是非搶占的,我們認為PRT策略是提供可預測響應時間的最佳方法。然而,在每個GPU命令組邊界進行調度決策不可避免地會帶來開銷,如下圖(a)所示。

    HT策略減少了這種調度開銷,稍微減少了可預測的響應時間。如果(i)當前正在執行的GPU命令組是由同一任務提交的,并且(ii)等待隊列中沒有更高優先級的任務,則允許立即將GPU命令組提交給GPU。否則,它們必須以與PRT策略相同的方式暫停。在中斷時,只有當GPU聯機列表為空(GPU空閑)時,等待隊列中優先級最高的任務才會被喚醒。

    下圖(b)描述了在HT策略下如何調度下圖(a)中使用的同一組GPU命令組。與PRT策略不同,MP任務的第二個實例可以立即提交其GPU命令組,因為當前正在執行的GPU命令組是由其自身發出的。MP任務的這兩個GPU命令組可以連續執行,而不會產生空閑時間,HP任務的兩個GPU命令組也是如此。因此,HT策略更適用于面向吞吐量的任務,但HP任務被MP任務阻塞更長的時間。這是一種權衡,如果優先級反轉至關重要,則PRT策略更合適。

    TimeGraph中GPU調度的示例。

    TimeGraph支持GPU執行時間預測,使用基于歷史的方法——搜索與傳入GPU命令序列匹配的以前GPU命令序列的記錄,適用于二維,但需要調查三維和計算。

    TimeGraph開啟后,在各方面的性能影響如下:

    在后臺中與圖形Bomb競爭的三維游戲幀速率。

    干擾時間。

    獨立設備的性能。

    總之,TimeGraph支持在多任務環境中對GPU應用程序進行優先級排序和隔離:

    • 設備驅動程序解決方案:不修改用戶空間。
    • GPU命令的調度。
    • 保留GPU資源使用。

    16.3.3.5 GPU Scheduling

    要開始理解在多個獨立應用程序之間共享單個GPU的含義和困難,我們首先必須了解GPU通常如何處理單個應用程序的任務調度。調度是跨時間和空間維度考慮的,即決定任務應該在何時何地執行。時間調度由上述FCFS算法確定;當任務到達隊列的頭部時,它將得到服務(如果有足夠的資源可用)。這種調度也是非搶占性的——一旦將一個塊調度到設備,就不能為另一個塊搶占執行。此外,這種非搶占也適用于任務級別。空間調度根據任務資源需求和SMX上的資源可用性的雙重考慮,確定任務可以執行的SMX單元。

    在硬件調度級別,最小的可調度單元是線程塊,意味著設備上的某些SMX必須能夠滿足整個塊的資源需求,以便將其分派執行。此外,一個內核的所有線程塊都不必一次處理,這就引入了執行波的概念。如果我們將塊調度視為一個瞬時過程,那么每個wave都會根據塊需求和設備資源可用性來調度最大允許的塊數。一旦安排了wave,所有剩余的塊都將保留在執行隊列中,直到設備上的其他執行完成,從而釋放資源。

    有許多因素決定了有多少塊可以組成一個完整的執行wave。內核線程塊的配置是一個重要因素,因為這決定了所需的每個資源的數量。另一個關鍵限制因素是對每個SMX允許的駐留線程塊數量的內置約束。必須注意這種約束,因為在達到最大駐留塊數之前,其他資源通常可能不會完全耗盡,在由具有不同配置和資源需求的內核組成的并發執行場景中尤其可能。下表列出了多代NVIDIA GPU上每個SMX的資源限制。

    Compute Capability 3.5 5.2 6.0 7.0
    Maximum Threads / SMX 2048 2048 2048 2048
    Maximum Thread Blocks / SMX 16 32 32 32
    Maximum Threads per Block 1024 1024 1024 1024
    Maximum Registers / SMX 65536 65536 65536 65536
    Shared Memory per SMX 48 KB 96 KB 64 KB 96 KB

    首先,線程塊在所有SMX單元中的分布確保了某些技術(例如功率選通)無法可靠地應用于提高功率效率。其次,空間調度方法會導致不確定性模式,從而消除了通過將某些塊映射到特定SMX來提高利用率和/或效率的可能方法。最后,在并發場景下,獨立內核的不同資源需求導致SMX單元集的利用率不對稱。

    將上述調度策略應用于多個獨立應用程序共享GPU設備的場景,需要消除資源沖突并確保某些屬性,例如公平性。在GPU計算中實現這一點的一種方法是通過構造流。

    GPU流表示CPU和GPU之間的獨立執行流,類似于CPU線程。在流中,操作按調用順序(即FCF)順序執行。在不同的流之間,根據資源的可用性,可以并行或交錯執行操作。下圖顯示了多流場景的抽象表示,流中的操作被調度到單個工作隊列中,只有當操作到達流隊列的前端時,才會將其調度到下一級設備調度器。

    多內核調度層次結構。

    下一級調度器表示執行隊列(對于內核)或復制隊列(對于內存傳輸)。同樣,這些隊列中的操作是以FCFS方式調度的,但由于某些設備狀況,會出現一些警告。下表是一些GPU調度規則(是針對NVIDIA Jetson TX2體系結構根據經驗得出的一組調度規則,并針對其他幾種NVIDIA體系結構進行了驗證)。

    標識符 規則
    G1 當調用關聯的CUDA API函數(內存傳輸或內核啟動)時,復制操作或內核在其流的流隊列中排隊。
    G2 當內核到達其流隊列的頭部時,它將被排入EE隊列。
    G3 EE隊列頭部的內核一旦完全調度,就會從該隊列中退出隊列。
    G4 一旦內核的所有塊完成執行,內核就會從其流隊列中退出隊列。
    X1 只有位于EE隊列頭部的內核塊才有資格分配。
    R1 只有在滿足資源約束的情況下,位于EE隊列頭部的內核塊才有資格被分配。
    R2 只有在某些SM上有足夠的可用線程資源時,位于EE隊列頭部的內核塊才有資格被分配。
    R3 只有在某些SM上有足夠的共享內存資源可用時,位于EE隊列頭部的內核塊才有資格被分配。
    C1 復制操作到達其流隊列的頭部時,將在CE隊列上排隊。
    C2 CE隊列頭部的復制操作有資格分配給CE。
    C3 一旦將拷貝分配給GPU上的CE,CE隊列頭部的拷貝操作將從CE隊列中退出。
    C4 CE完成復制后,復制操作將從其流隊列中退出隊列。
    N1 當對于每個其他流隊列,要么隊列為空,要么其頭部的內核在Kk之后啟動時,位于空流隊列頭部的內核Kk被排隊到EE隊列上。
    N2 非空流隊列頭部的內核Kk不能在EE隊列上排隊,除非空流隊列為空或其頭部的內核是在Kk之后啟動的。
    A1 內核只能在與其流優先級匹配的EE隊列上排隊。
    A2 只有當所有高優先級EE隊列(優先級高過優先級低)為空時,任何EE隊列頭部的內核塊才有資格分配。

    理解這些調度規則對于實現最大資源利用率和高系統吞吐量至關重要。這些規則共同說明,一旦內核的塊開始調度到GPU(通過X1),在調度完第一個內核的所有塊之前,無法調度其他內核(G3)。當第一個內核由于不滿足其中一個資源規則(R1-R3)而在執行隊列中暫停時,最有可能出現這種情況。然而,執行隊列中后續內核的配置完全可能滿足這些資源規則。因此,將錯失提高設備利用率的機會,從而需要更精細的執行隊列調度方法。在不進行類似解釋的情況下,我們聲稱復制隊列也存在這些考慮因素,因此需要對替代調度策略進行類似的檢查。

    先前的研究為進一步分析GPU資源的利用情況提供了動力,以實現高性能和能效。Hong和Kim在論文沿著兩個主軸對應用程序進行了分類,即計算約束的應用程序和內存約束的應用程序,并表明性能、利用率和能效之間的關系與此特征相關。受計算限制的應用程序具有強伸縮性。對于固定的問題大小,如果給應用程序更多的處理內核,它將顯示與內核數量成比例的加速(即在更短的時間內完成相同數量的工作)。另一方面,內存受限的應用程序具有弱伸縮性。在這種情況下,性能受到固定問題大小的限制,如果問題大小按比例增加(即在相同時間內完成更多工作),則添加更多處理器只能提高性能。

    Hong和Kim展示了這些縮放特性與各種GPU內核的性能和功耗之間的關系,以得出在應用程序中使用的最佳內核數。這種方法聲稱的好處是,根據應用類型,在不同的利用率下可以實現最佳的能效。對于受計算限制的應用程序,最佳點是充分利用率,而對于受內存限制的應用程序,最佳點是低于此值。顯而易見的結論是,許多應用程序不需要完全補充GPU資源來實現最佳性能。此外,該論文中方法的目標是開發一個模型,以確定單個內核的最佳內核數量,并成功證明了能量效率的提高。然而,該方法沒有解決并發執行場景,也沒有提出一種利用未充分利用的GPU資源潛力的方法。

    下圖所示的簡單場景,在左邊,展示了單個內核執行的概念場景(即獨立內核之間沒有設備共享)以及由此產生的性能和利用率。特別是該設備的利用率僅為62.5%,計劃內核的最大生成時間為4個時間單位。然而,當考慮并發執行時,如下圖右所示,利用率增加到83.3%,最大完工時間減少到3個時間單位。雖然所示場景是對復雜調度問題的過度簡化,特別是考慮到上面討論的調度規則和約束,但它提供了分析和開發新技術以最大限度地利用GPU計算架構能力的最初動機。

    串行(左)和并發(右)執行的性能和利用率比較。

    在考慮典型GPU設備的功耗特性時,進一步驗證了利用并發性和最大化設備利用率的好處。如上所述,工作以循環或半循環的方式分配給設備上的SMX單元。因此,整個設備在執行期間通電,功率選通等策略通常不適用。當然,有多種因素決定內核應用程序的峰值功耗,包括線程操作、內存訪問等,但我們觀察到,峰值通常與所利用的主設備資源的總體百分比無關。下圖說明了這樣一個示例。

    內核運行時間(左)和GPU功耗(右),用于增加塊數和設備利用率。

    在本例中,內核的最高性能是在GPU利用率不足的情況下實現的。類似地,峰值功耗從最低利用率水平到可能的最大利用率增長約5%,增幅可以忽略不計。這有助于從經驗上驗證中的觀察結果,并允許我們提出這樣的主張:提高系統吞吐量的方法,即在較短時間內完成更多任務,將對系統能效產生積極影響,而無需引入任何明確管理功耗的技術。鑒于這些機會,研究的主要重點是獨立GPU任務的優化調度,以提高系統吞吐量和資源利用率。除了上述能效外,這種方法的好處還包括通過平均標準化周轉時間來衡量改善各自的任務績效。


    OS的GPU調度可從用戶空間內核空間考量,它們各有4種調度方式。

    首先考慮用戶空間的GPU調度器的軟件體系結構,下圖描述了幾種體系結構。

    在用戶空間中實現的API驅動的GPU調度程序的幾種軟件體系結構。

    • Centralized Scheduling With Enforcement(集中調度與實施)

    上圖(a)描述了用戶空間中GPU調度的通用軟件體系結構。GPGPU API調用被發出到每個任務中的GPGPU存根庫,存根庫通過IPC通道(例如UNIX域套接字、TCP/IP套接字等)將API請求重定向到GPGPU調度守護程序,該守護程序根據集中式調度策略為請求提供服務。守護進程自行執行所有API調用,強制執行所有調度決策。

    好處:

    1、由于決策集中,調度策略易于實施。

    2、由于計劃的API調用由GPGPU調度守護程序本身執行,因此執行調度決策。

    缺點:

    1、守護進程必須包含或能夠加載組成任務的GPU內核代碼。這可以在編譯守護程序時完成。GPU內核代碼的動態加載也是可能的,但實現起來并不簡單。

    2、由于任務和守護進程之間的消息傳遞,IPC引入了開銷。

    3、除非使用任何內存重映射技術,GPU內核數據必須通過IPC通道傳輸。數據密集型GPGPU應用程序(例如,由攝像機提供數據的行人檢測應用程序)的性能較差。

    4、必須對守護進程本身進行調度。這會帶來額外的調度程序開銷。此外,除非RTOS提供一種機制,使守護進程可以從其組成部分繼承優先級任務,可調度性分析不是直截了當的。可以通過提高守護程序的優先級(會影響可調度性分析)來繞過此限制,或者可以為守護程序專門保留一個CPU(會導致其他工作丟失一個CPU)。這兩種方法都不可取。

    GViM、gVirtuS、vCUDA、rCUDA和MPS采用這種流行的體系結構。然而,這些都沒有實現實時GPU調度策略。通過IPC通道傳輸GPU內核數據的開銷可能會導致數據量大的應用程序無法接受的性能。

    • Centralized Scheduling Without Enforcement(集中調度,無需強制執行)

    上圖(b)描述了另一個基于守護進程的調度器。GPGPU API調用被插入的庫截獲,對于每個調用,庫都會通過IPC通道向GPU調度守護程序發出相應GPU引擎的請求。庫等待每個請求被授予,守護程序根據集中式調度策略授予請求,一旦授予了必要的資源,插入的庫就會將截獲的API調用傳遞給原始GPGPU運行時。

    好處:

    1、易于實現,因為調度決策是集中的,GPU內核代碼對于每個組成任務都是本地的。

    2、GPU內核數據不會通過IPC通道復制。數據密集型GPGPU應用程序性能良好。

    缺點:

    1、無法執行GPU調度決策。行為不當或惡意的任務可能會繞過插入庫并直接訪問GPGPU運行時。

    2、IPC引入了消息傳遞開銷。

    3、必須安排守護程序本身。

    由于GPU內核輸入和輸出數據不穿過IPC通道,這種體系結構犧牲了調度決策的執行,以實現數據密集型應用程序的性能優勢。此外,這是8種GPU調度體系結構中最容易實現的,是基于Windows 7的PTask原型所采用的體系結構,PTask是一種非實時GPU調度程序。RGEM也是一種實時GPU調度器,它也采用了這種體系結構。

    • Cooperative Scheduling With Enforcement(帶執行的協作調度)

    上圖(c)描述了協作GPU調度器和GPGPU運行時守護程序的軟件體系結構。插入的庫攔截API調用,每個任務中庫的每個實例調用相同的GPU調度算法,該算法嵌入在插入的庫中。GPU調度程序狀態的單個實例存儲在共享內存中,插入的庫將API調用傳遞給守護程序以實際執行。

    好處:

    1、GPU調度是高效的,因為每個任務都可以直接訪問GPU調度程序狀態。

    2、調度決策執行力弱。盡管GPGPU運行時守護程序集中了對GPU的所有訪問,但行為不當或惡意的任務可能會繞過協作GPU調度程序直接向守護程序發出工作。

    缺點:

    1、對共享GPU調度程序狀態的訪問必須在任務之間進行協調(或同步)。根據使用的同步機制,任務可能需要在執行調度算法時非搶占地執行(以避免死鎖)。這需要RTO的支持或訪問臨時禁用搶占的特權CPU指令。

    2、行為不當或惡意的任務可能會損壞GPU調度程序狀態,因為它可能會覆蓋共享內存中的任何數據。從此類故障中恢復可能很困難。

    3、行為不當或惡意的任務可能繞過GPU調度程序,直接向GPGPU運行時守護程序發出工作,除非守護程序具有驗證請求的機制。

    4、IPC引入了消息傳遞開銷。

    5、GPU內核數據必須通過IPC通道傳輸。

    6、必須安排守護程序本身。

    該體系結構中與IPC相關的開銷消除了協作調度的任何潛在好處。此外,由于可以繞過GPU調度器,執行調度決策的能力被削弱。這種體系結構與集中式調度(上圖a)相比沒有明顯的優勢。

    • Cooperative Scheduling Without Enforcement(無強制的協同調度)

    上圖(d)描述了協作GPU調度程序的軟件體系結構,無需使用守護程序。插入的庫攔截API調用,與之前一樣,任務協同執行相同的GPU調度算法,并在相同的共享調度程序狀態下運行。截獲的API調用在計劃時傳遞給原始GPGPU運行時。

    好處:

    1、沒有IPC管理費用。數據密集型GPGPU應用程序性能良好。

    2、沒有要調度的守護程序。這簡化了實時分析,并減少了對RTO的支持。

    3、高效的GPU調度。

    壞處:

    1、必須協調對GPU調度程序狀態的訪問。

    2、GPU調度程序狀態容易損壞。

    3、無法執行GPU調度決策。

    這是研究的最有效的用戶空間體系結構。它避免了所有IPC開銷,避免了守護進程帶來的所有開銷和分析難題。然而,它也是所有8種體系結構中最脆弱的,必須信任任務:不要繞過GPU調度程序和不損壞GPU調度程序狀態。

    接下來探索內核空間的GPU調度。

    用戶空間中的GPU調度有幾個弱點。一個缺點是,我們可能無法充分保護GPU調度程序數據結構。協作調度方法就是這種情況,其中GPU調度程序狀態可能會被行為錯誤或惡意任務破壞。然而,用戶空間調度的最大弱點是我們無法與底層RTO緊密集成,可能會妨礙我們以任何程度的信心實現正確的實時系統。RTOS可以提供一些機制,允許實時任務通過用戶空間操作影響其他任務的調度優先級(例如,具有優先級修改進度機制的實時鎖定協議,以及直接操縱優先級的系統調用)。可以利用這些為用戶空間GPU調度器添加一些實時確定性。然而,這些機制可能不足以最小化,更重要的是,限制GPU相關的優先級反轉。

    我們解決很多問題的能力受到用戶空間的嚴重限制。通過將GPU調度器與CPU調度器和RTOS內核中的其他操作系統組件(如中斷處理服務)緊密集成,可以最好地解決這些問題。這些問題促使我們考慮內核空間GPU調度器。下圖描述了內核空間GPU調度器的幾種高級軟件體系結構。假設所有方法都受益于與RTO緊密集成的能力。

    • Centralized Scheduling With Enforcement(集中調度與實施)

    上圖(a)描述了具有集中式調度程序守護程序的軟件體系結構。該體系結構與圖3.1(a)中所示的體系結構非常相似,其功能大致相同。然而,GPU調度守護程序現在從內核空間運行。

    好處:

    1、GPU內核數據不會通過IPC通道復制。這是可能的,因為GPU調度守護程序可以直接訪問其組成任務的用戶空間內存。數據密集型GPGPU應用程序性能良好。

    2、調度策略是集中的。

    3、執行計劃決策。

    缺點:

    1、內核空間GPGPU運行時可能不可用。所有制造商提供的GPGPU運行時僅在用戶空間中運行。

    2、必須對守護進程本身進行調度。會帶來額外的系統開銷,然而,從內核空間來看,可以更靈活地對守護進程進行適當的優先級排序,以確保實時確定性。

    3、守護進程必須包括或能夠加載組成任務的GPU內核代碼。

    4、IPC引入了消息傳遞開銷。

    這種體系結構得益于強制的集中式調度,無需在IPC通道上穿梭GPU內核輸入和輸出數據。由于消息傳遞,這種方法仍然會遇到一些IPC通道開銷。然而,這種方法最大的缺點是實用性:內核空間GPGPU運行時通常不可用。正如Gdev(采用上圖a和d的混合架構)所示,雖不是一個無法克服的挑戰,但很困難。

    • Centralized Scheduling Without Enforcement(集中調度,無需強制執行)

    上圖(b)描述了另一個帶有GPU調度守護程序的軟件體系結構,其架構與用戶空間的(b)中的架構相匹配,只是守護進程現在在內核空間中運行。

    好處:

    1、使用通用用戶空間GPGPU運行時。

    2、調度策略是集中的。

    3、GPU內核數據未通過IPC通道復制。

    好處:

    1、無法執行GPU調度決策。

    2、IPC引入了消息傳遞開銷。

    3、必須調度守護程序本身。

    該體系結構在內核空間集中式調度和實際約束之間達成了妥協。調度決策是在內核空間內做出的,但由具有用戶空間GPGPU運行時的單個任務執行。因此,體系結構無法強制執行其調度決策,是基于Linux的PTask原型所采用的體系結構,PTask是一個非實時GPU調度器。

    • Cooperative Scheduling With Enforcement(帶執行的協作調度)

    上圖(c)描述了協作GPU調度器的軟件體系結構。GPGPU API調用被路由到存根庫,存根庫通過OS系統調用調用內核空間GPU調度程序。GPU調度程序狀態由所有任務共享,但存儲在內核空間數據結構中。計劃的API調用由內核空間GPGPU運行時使用調用任務的程序線程執行。

    好處:

    1、GPU調度程序狀態受保護。與協作用戶空間調度器不同,GPU調度器狀態在內核空間內受到保護,不會因行為不當或惡意的用戶空間任務而損壞。

    2、在內核空間中,對GPU調度程序狀態的同步訪問是微不足道的。在更新GPU調度程序數據結構時,無需升級權限以非搶占方式執行。

    3、GPU調度是有效的。

    4、執行計劃決策。

    好處:

    1、需要內核空間GPGPU運行時。

    這是從性能角度研究的8種體系結構中最強的一種,協作調度決策是高效的,由RTO執行。GPGPU運行時是使用調用任務的程序堆棧在內核空間中執行的,而不是單獨調度的守護進程,沒有IPC開銷,唯一限制是對內核空間GPGPU運行時的依賴。

    • Cooperative Scheduling Without Enforcement(無強制的協同調度)

    上圖(d)描述了協作GPU調度器的另一種軟件體系結構。插入的庫攔截API調用,并通過系統調用調用內核空間GPU調度器。與之前一樣,GPU調度程序狀態由所有任務共享,并保護其不受行為不當和惡意任務的影響。要計劃API調用,GPU調度程序將控制權返回到插入的庫。插入的庫使用用戶空間GPGPU運行時執行計劃的API調用。

    好處:

    1、使用通用用戶空間GPGPU運行時。

    2、GPU調度程序狀態受保護。

    3、訪問GPU調度程序狀態很容易同步。

    4、GPU調度是有效的。

    壞處:

    1、無法執行GPU調度決策。

    為了支持用戶空間GPGPU運行時,此體系結構犧牲了強制功能,通過直接訪問GPGPU運行時,信任任務不會繞過插入的庫。盡管有此限制,但從性能角度來看,它仍然是一個強大的體系結構。與前面的方法一樣,調度決策是有效的。此外,沒有與IPC或守護進程相關的開銷。對于愿意實現操作系統級代碼的研究人員或開發人員來說,這種體系結構是最實用的高性能選項。

    再聊聊其它GPU調度相關的雜項技術。

    下圖是名為vHybrid的CPU-GPU調度架構:

    下圖是使用klmirqd的GPU微線程調度基礎架構:

    使用GPUSync的任務的復雜執行依賴鏈示例:

    混合平臺配置:

    下半部分開銷處理的高級視圖:

    間接開銷增加引擎臨界區長度的情況:

    描述回調開銷的調度:

    更多詳情看參閱:


    16.4 GPU驅動

    16.4.1 NVIDIA

    16.4.1.1 Turing架構

    Turing代表了十多年來最大的架構飛躍,它提供了一種新的核心GPU架構,使PC游戲、專業圖形應用程序和深度學習推斷在效率和性能方面取得了重大進步。

    使用新的基于硬件的加速器和混合渲染方法,Turing融合了光柵化、實時光線跟蹤、AI和模擬,以實現PC游戲中難以置信的真實感、神經網絡支持的驚人新效果、電影質量的交互體驗,以及創建或導航復雜3D模型時的流體交互。

    在核心體系結構中,Turing顯著提升圖形性能的關鍵促成因素是具有改進著色器執行效率的新GPU處理器(流式多處理器SM)體系結構,以及包括支持最新GDDR6內存技術的新內存系統體系結構。

    圖像處理應用程序(如ImageNet Challenge)是深度學習的首批成功案例之一,因此AI有潛力解決許多重要的圖形問題也就不足為奇了。Turing的Tensor Cores為一套新的基于深度學習的神經服務提供動力,除了為基于云的系統提供快速AI推斷外,還為游戲和專業圖形提供驚人的圖形效果。

    Turing的革命性新特性。

    NVIDIA Turing是世界上最先進的GPU體系結構,高端TU102 GPU包括在臺積電12納米FFN(FinFET NVIDIA)高性能制造工藝上制造的186億個晶體管。GeForce RTX 2080 Ti Founders Edition GPU具有以下優異的計算性能:

    • 14.2峰值單精度(FP32)性能的TFLOPS。
    • 28.5峰值半精度(FP16)性能的TFLOPS。
    • 14.2 TIPS1通過獨立的整數執行單元與FP并發。
    • 113.8 Tensor TFLOPS。
    • 10 Giga射線/秒。
    • 78 Tera RTX-OPS。

    Quadro RTX 6000提供了專為專業工作流設計的卓越計算性能:

    • 16.3峰值單精度(FP32)性能的TFLOPS。
    • 32.6峰值半精度(FP16)性能的TFLOPS。
    • 16.3 TIPS通過獨立的整數執行單元與FP并發。
    • 130.5張量TFLOPS。
    • 10 Giga射線/秒。
    • 84 Tera RTX-OPS。

    此外,新的特性還有新的Streaming Multiprocessor (SM)、Turing Tensor Core、實時光追加速、新的著色改進(Mesh Shading、Variable Rate Shading、Texture-Space Shading 、Multi-View Rendering)、Deep Learning、GDDR6等等。

    Turing架構的TU102 GPU。

    Turing TU102/TU104/TU106 Streaming Multiprocessor (SM)。

    分析許多工作負載顯示,平均每100個浮點操作有36個整數操作。

    新的共享內存體系結構。

    與Pascal相比,Turing Shading在許多不同工作負載下的性能加速比。

    新的Turing Tensor Core為人工智能推理提供了多精度。

    Turing GDDR6。

    圖靈GPU除了新的GDDR6內存子系統之外,還添加了更大、更快的二級緩存。TU102 GPU附帶6 MB二級緩存,是TITAN Xp中使用的上一代GP102 GPU提供的3 MB二級緩存的兩倍。TU102還提供了比GP102高得多的二級緩存帶寬。與上一代NVIDIA GPU一樣,圖靈中的每個ROP分區包含八個ROP單元,每個單元可以處理一個顏色樣本。完整的TU102芯片包含12個ROP分區,總共96個ROP。

    NVIDIA GPU利用幾種無損內存壓縮技術來減少數據寫入幀緩沖存儲器時的內存帶寬需求。GPU的壓縮引擎有各種不同的算法,這些算法根據數據的特性確定壓縮數據的最有效方式,減少了寫入內存和從內存傳輸到二級緩存的數據量,并減少了客戶端(如紋理單元)和幀緩沖區之間傳輸的數據量。圖靈進一步改進了Pascal最先進的內存壓縮算法,在GDDR6原始數據傳輸速率增加的基礎上,進一步提升了有效帶寬。如下圖所示,原始帶寬的增加和通信量的減少意味著與Pascal相比,Turing上的有效帶寬增加了50%,對于保持體系結構平衡和支持新Turing SM體系結構提供的性能至關重要。

    與基于Pascal GP102的1080 Ti相比,基于Turing TU102的RTX 2080 Ti的內存子系統和壓縮(流量減少)改進提供了大約50%的有效帶寬改進。

    此外,Turing還開創性地增加了實時光線追蹤的支持,引入了混合管線:

    Turing GPU可以加速以下許多渲染和非渲染操作中使用的光線跟蹤技術:

    • 反射和折射。
    • 陰影和環境光遮擋。
    • 全局照明。
    • 即時離線光照貼圖烘焙。
    • 美麗的照片和高質量的預覽。
    • 用于注視點VR渲染的主光線。
    • 遮擋剔除。
    • 物理、碰撞檢測、粒子模擬。
    • 音頻模擬(例如,NVIDIA VRWorks音頻構建在OptiX API之上)。
    • AI可見性查詢。
    • 引擎內路徑跟蹤(非實時)生成參考屏幕截圖,用于調整實時渲染技術和去噪器、材質合成和場景照明。

    在沒有硬件加速的情況下,光線跟蹤需要每條光線上千個軟件指令槽,以便在BVH結構中連續測試較小的包圍盒,直到可能碰到三角形為止。這是一個計算密集型的過程,如果沒有基于硬件的光線跟蹤加速,就不可能在GPU上實時完成(見下圖)。


    下圖說明了傳統的光柵化和著色過程,3D場景被光柵化并轉換為屏幕空間中的像素,對像素進行可見性測試、外觀著色測試和深度測試。所有操作都發生在相同的屏幕空間像素網格上,在相同的像素上。

    使用紋理空間著色(Texture Space Shading,TSS),可見性采樣(光柵化和z測試)和外觀采樣(著色)這兩個主要操作可以解耦,并以不同的速率、在不同的采樣網格上、甚至在不同的時間軸上執行。著色過程不再直接綁定到屏幕空間像素,而是發生在紋理空間中。在下圖中,幾何體仍被光柵化以生成屏幕空間像素,可見性測試仍在屏幕空間中進行。然而,不是在屏幕空間中著色,而是發現需要覆蓋輸出像素的紋理。換句話說,屏幕空間像素的足跡映射到單獨的紋理空間中,并在紋理空間中對關聯的texel進行著色。映射到紋理空間是一種標準的紋理映射操作,對LOD和各向異性過濾等具有相同的控制。為了生成最終的屏幕空間像素,我們從著色紋理中采樣。紋理是根據示例請求按需創建的,僅為引用的紋理生成值。

    TSS的一個示例用例是提高VR渲染的效率,下圖顯示了VR渲染中TSS的一個示例用例。在VR中,渲染一對立體圖像,左眼可見的幾乎所有元素也顯示在右眼視圖中。使用TSS,我們可以對整個左眼視圖進行著色,然后通過從完成的左眼視圖采樣來渲染右眼視圖。右眼視圖只需在未找到有效樣本的情況下對新紋理進行著色(例如,從左眼角度看,背景對象在視圖中被遮擋,但右眼可見)。

    多視圖渲染(MVR)允許開發人員從多個視點高效地繪制場景,甚至以不同姿勢繪制角色的多個實例,所有這些都在一個通道中完成。Turing硬件每個過程最多支持四個視圖,在API級別最多支持32個視圖。通過只提取一次幾何體并對其著色,Turing可以在渲染多個版本時以最佳方式處理三角形及其關聯的頂點屬性。當通過D3D12視圖實例化API訪問時,開發人員只需使用變量SV_ViewID來索引不同的變換矩陣、引用不同的混合權重或控制他們喜歡的任何著色器行為,這些行為取決于他們正在處理的視圖。

    下圖顯示了200° FOV HMD的配置,其中使用了兩個傾斜面板,需要MVR更大的表現力。MVR的靈活性也有利于支持標準立體VR顯示器的更精確校準,以與單個用戶的面部對齊。在立體渲染中,眼睛只是在X方向上相互偏移的簡單假設并不完全正確,實際上還有一些額外的不對稱,需要獨立投影才能獲得最高的保真度對齊。

    200°FOV HMD,其中使用兩個傾斜面板,并受益于MVR。

    MVR單通道級聯陰影貼圖渲染。

    16.4.1.2 Ampere架構

    NVIDIA Ampere體系架構GPU系列的最新成員GA102和GA104,GA102和GA104是新英偉達“GA10x”級Ampere架構GPU的一部分,GA10x GPU基于革命性的NVIDIA Turing GPU架構。

    GeForce RTX 3090是GeForce RTX系列中性能最高的GPU,專為8K HDR游戲設計。憑借10496個CUDA內核、24GB GDDR6X內存和新的DLSS 8K模式,它可以在8K@60fps。GeForce RTX 3080的性能是GeForce RTX 2080的兩倍,實現了GPU有史以來最大的一代飛躍,GeForce RTX 3070的性能可與NVIDIA上一代旗艦GPU GeForce RTX 2080 Ti相媲美,GA10x GPU中新增的HDMI 2.1和AV1解碼功能允許用戶使用HDR以8K的速度傳輸內容。

    NVIDIA A40 GPU是數據中心在性能和多工作負載能力方面的一次革命性飛躍,它將一流的專業圖形與強大的計算和AI加速相結合,以應對當今的設計、創意和科學挑戰。A40具有與RTX A6000相同的內核數量和內存大小,將為下一代虛擬工作站和基于服務器的工作負載提供動力。NVIDIA A40的能效比上一代高出2倍,它為專業人士帶來了光線跟蹤渲染、模擬、虛擬制作等最先進的功能。

    Ampere GA10x體系結構具有巨大的飛躍。

    GA102的關鍵特性有2倍FP32處理、第二代RT Core、第三代Tensor Core、GDDR6X和GDDR6內存、PCIe Gen 4等。

    與之前的NVIDIA GPU一樣,GA102由圖形處理集群(Graphics Processing Cluster,GPC)、紋理處理集群(Texture Processing Cluster,TPC)、流式多處理器(Streaming Multiprocessor,SM)、光柵操作器(Raster Operator,ROP)和內存控制器組成。完整的GA102 GPU包含7個GPC、42個TPC和84個SM。

    GPC是主要的高級硬件塊,所有關鍵圖形處理單元都位于GPC內部。每個GPC都包括一個專用的光柵引擎,現在還包括兩個ROP分區(每個分區包含八個ROP單元),是NVIDIA Ampere Architecture GA10x GPU的一個新功能。GPC包括六個TPC,每個TPC包括兩個SM和一個PolyMorph引擎。

    GA102 GPU還具有168個FP64單元(每個SM兩個),FP64 TFLOP速率是FP32操作TFLOP速率的1/64。包括少量的FP64硬件單元,以確保任何帶有FP64代碼的程序都能正確運行,包括FP64 Tensor Core代碼。

    GA10x GPU中的每個SM包含128個CUDA核、四個第三代Tensor核、一個256 KB的寄存器文件、四個紋理單元、一個第二代光線跟蹤核和128 KB的L1/共享內存,這些內存可以根據計算或圖形工作負載的需要配置為不同的容量。GA102的內存子系統由12個32位內存控制器組成(共384位),512 KB的二級緩存與每個32位內存控制器配對,在完整的GA102 GPU上總容量為6144 KB。

    Ampere架構還對ROP執行了優化。在以前的NVIDIA GPU中,ROP綁定到內存控制器和二級緩存。從GA10x GPU開始,ROP是GPC的一部分,通過增加ROP的總數和消除掃描轉換前端和光柵操作后端之間的吞吐量不匹配來提高光柵操作的性能。每個GPC有7個GPC和16個ROP單元,完整的GA102 GPU由112個ROP組成,而不是先前在384位內存接口GPU(如前一代TU102)中可用的96個ROP。此方法可改進多采樣抗鋸齒、像素填充率和混合性能。

    在SM架構方面,圖靈SM是NVIDIA的第一個SM體系結構,包括用于光線跟蹤操作的專用內核。Volta GPU引入了張量核,Turing包括增強的第二代張量核。Turing和Volta SMs支持的另一項創新是并行執行FP32和INT32操作。GA10x SM改進了上述所有功能,同時還添加了許多強大的新功能。與以前的GPU一樣,GA10x SM被劃分為四個處理塊(或分區),每個處理塊都有一個64 KB的寄存器文件、一個L0指令緩存、一個warp調度程序、一個調度單元以及一組數學和其他單元。這四個分區共享一個128 KB的一級數據緩存/共享內存子系統。與每個分區包含兩個第二代張量核、總共八個張量核的TU102 SM不同,新的GA10x SM每個分區包含一個第三代張量核,總共四個張量核,每個GA10x張量核的功能是圖靈張量核的兩倍。與Turing相比,GA10x SM的一級數據緩存和共享內存的組合容量要大33%。對于圖形工作負載,緩存分區容量是圖靈的兩倍,從32KB增加到64KB。

    GA10x Streaming Multiprocessor (SM) 。

    GA10x SM繼續支持圖靈支持的雙速FP16(HFMA)操作。與TU102、TU104和TU106圖靈GPU類似,標準FP16操作由GA10x GPU中的張量核處理。FP32吞吐量的比較X因子如下表:

    Turing GA10x
    FP32 1X 2X
    FP16 2X 2X

    如前所述,與前一代圖靈體系結構一樣,GA10x具有用于共享內存、一級數據緩存和紋理緩存的統一體系結構。這種統一設計可以根據工作負載進行重新配置,以便根據需要為L1或共享內存分配更多內存。一級數據緩存容量已增加到每個SM 128 KB。在計算模式下,GA10x SM將支持以下配置:

    • 128 KB L1 + 0 KB Shared Memory
    • 120 KB L1 + 8 KB Shared Memory
    • 112 KB L1 + 16 KB Shared Memory
    • 96 KB L1 + 32 KB Shared Memory
    • 64 KB L1 + 64 KB Shared Memory
    • 28 KB L1 + 100 KB Shared Memory

    Ampere架構的RT Core比Turing的RT Core的射線/三角形相交測試速度提高了一倍:

    GA10x GPU通過一種新功能增強了先前NVIDIA GPU的異步計算功能,該功能允許在每個GA10x GPU SM中同時處理RT Core和圖形或RT Core和計算工作負載。GA10x SM可以同時處理兩個計算工作負載,并且不像以前的GPU代那樣僅限于同時計算和圖形,允許基于計算的降噪算法等場景與基于RT Core的光線跟蹤工作同時運行。

    相比Turing架構,NVIDIA Ampere體系結構在渲染同一游戲中的同一幀時,可大大提高性能:

    上:基于圖靈的RTX 2080超級GPU渲染Wolfenstein的一幀:僅使用著色器核心(CUDA核心)、著色器核心+RT核心和著色器核心+RT核心+張量核心的Youngblood。請注意,在添加不同的RTX處理內核時,幀時間逐漸減少。

    下:基于安培體系結構的RTX 3080 GPU渲染一幀Wolfenstein:Youngblood僅使用著色器核心(CUDA核心)、著色器核心+RT核心和著色器核心+RT核心+張量核心。

    GA10x RT Core使光線/三角形相交測試速率比Turing RT Core提高了一倍,還添加了一個新的插值三角形位置加速單元,以協助光線跟蹤運動模糊操作。

    在啟用稀疏性的情況下,GeForce RTX 3080提供的FP16 Tensor堆芯操作峰值吞吐量是GeForce RTX 2080 Super的2.7倍,后者具有密集的Tensor堆芯操作:

    細粒度結構化稀疏性使用四取二非零模式修剪訓練權重,然后是微調非零權重的簡單通用方法。對權重進行壓縮,使數據占用和帶寬減少2倍,稀疏張量核心操作通過跳過零使數學吞吐量加倍。(下圖)

    下圖顯示了GDDR6(左)和GDDR6X(右)之間的數據眼(data eye)比較,通過GDDR6X接口可以以GDDR6的一半頻率傳輸相同數量的數據,或者,在給定的工作頻率下,GDDR6X可以使有效帶寬比GDDR6增加一倍。

    GDDR6X使用PAM4信令提高了性能和效率。

    為了解決PAM4信令帶來的信噪比挑戰,開發了一種名為MTA(最大傳輸消除,見下圖)的新編碼方案,以限制高速信號的轉移。MTA可防止信號從最高電平轉換到最低電平,反之亦然,從而提高接口信噪比。它是通過在編碼管腳上傳輸的字節中為每個管腳分配一部分數據突發(時間交錯),然后使用明智選擇的碼字將數據突發的剩余部分映射到沒有最大轉換的序列來實現的。此外,還引入了新的接口培訓、自適應和均衡方案。最后,封裝和PCB設計需要仔細規劃和全面的信號和電源完整性分析,以實現更高的數據速率。

    在傳統的存儲模型中,游戲數據從硬盤讀取,然后從系統內存和CPU傳輸,然后再傳輸到GPU,使得IO常常成為游戲的性能瓶頸:

    使用傳統的存儲模型,游戲解壓縮可以消耗Threadripper CPU上的所有24個內核。現代游戲引擎已經超過了傳統存儲API的能力。需要新一代的輸入/輸出體系結構。數據傳輸速率為灰色條,所需CPU內核為黑色/藍色塊。需要壓縮數據,但CPU無法跟上:

    NVIDIA RTX IO插入Microsoft即將推出的DirectStorage API,這是一種新一代存儲體系結構,專為配備最先進NVMe SSD的游戲PC和現代游戲所需的復雜工作負載而設計。總之,專門為游戲定制的流線型和并行化API可以顯著減少IO開銷,并最大限度地提高從NVMe SSD到支持RTX IO的GPU的性能/帶寬。具體而言,NVIDIA RTX IO帶來了基于GPU的無損解壓縮,允許通過DirectStorage進行的讀取保持壓縮,并傳送到GPU進行解壓縮。此技術可消除CPU的負載,以更高效、更壓縮的形式將數據從存儲器移動到GPU,并將I/O性能提高了兩倍。

    RTX IO提供100倍的吞吐量,20倍的CPU利用率。數據傳輸速率為灰色和綠色條,所需CPU內核為黑色/藍色塊。

    關卡加載時間比較。負載測試在24核Threadripper 3960x平臺上運行,原型Gen4 NVMe m.2 SSD,alpha軟件。

    16.4.1.3 Nouveau

    “Nouveau”是法語中“new”的意思。Nouveau項目旨在為nVidia卡構建高質量、免費/自由的軟件驅動程序,Nouveau由Linux內核KMS驅動程序(Nouveau)、Mesa中的Gallium3D驅動程序和Xorg DDX(xf86 video Nouveau)組成,內核組件也已移植到NetBSD。官網是https://nouveau.freedesktop.org/index.html。

    所有GPU均支持2D/3D加速(GA10x除外),大多數pre-Maxwell卡支持視頻解碼加速,在GM10x Maxwell、Kepler和Tesla G94-GT218 GPU上支持手動性能級別選擇。GM20x和更新的GPU重新鎖定的希望渺茫,因為如今固件需要NVIDIA簽署才能獲得必要的訪問權限。最近的更新是2021年1月:Linux 5.11中合并了GA10x內核模式設置支持。

    Nouveau最初使用Mesa 3D的直接渲染基礎設施(DRI)渲染3D計算機圖形,允許直接從3D應用程序使用圖形處理單元(GPU)加速3D繪制;但在2008年2月,DRI支持方面的工作停止了,并轉移到了新的Gallium3D。2013年9月23日Nvidia公開宣布,他們將發布一些關于其GPU的文檔,旨在解決影響Nvidia GPU與nouveau的現成可用性的領域。2016年7月9日,Red Hat員工Ben Skeggs提交了一個補丁,該補丁將對GeForce GTX 1070和GeForce GTX 1080品牌圖形卡上基于Pascal的GP104芯片的支持添加到Linux內核中。XDC2016介紹了2016年的現狀和未來的工作,FOSDEM上顯示了OpenCL的新工作狀態。2019年,NVidia提供了一些有關開普勒、麥克斯韋、帕斯卡和沃爾塔芯片組的文檔。


    除了Nouveau,支持NVIDIA顯卡的驅動還有pscnvDirectFB nVidia driverBeOS/Haiku nVidia driverUtah-GLXxfree 3.3.3 nvidia driver等。2022年5月,NVIDIA釋放了支持最新NV GPU的Linux內核驅動模塊,源碼在:NVIDIA Linux Open GPU Kernel Module Source


    16.4.2 AMD

    AMD Radeon軟件是一種用于高級Micro Devices圖形卡和APU的設備驅動程序和實用軟件包。其圖形用戶界面由Electron構建,并與64位Windows和Linux發行版兼容。

    該軟件以前稱為AMD Radeon Settings、AMD Catalyst和ATI Catalyst。AMD Radeon軟件旨在支持GPU或APU芯片上的所有功能塊,除了用于渲染的指令代碼外,還包括顯示控制器及其用于視頻解碼(統一視頻解碼器(UVD))和視頻編碼(視頻編碼引擎(VCE))的SIP塊。設備驅動程序還支持AMD TrueAudio,是一個用于執行聲音相關計算的SIP塊。

    Radeon軟件包括的功能:游戲配置文件管理、超頻和降頻、性能監控、錄制和流媒體、捕獲的視頻和屏幕截圖管理、軟件更新通知、升級advisor等。另外還支持多顯示器、視頻加速、音頻加速、電量節省、GPGPU、等功能,還支持D3D、Mantle、OpenGL、Vulkan、OpenCL等圖形API。

    GCN通過結合硬件和驅動程序支持,與緩存一致性一起引入了虛擬內存。虛擬內存消除了內存管理中最具挑戰性的方面,并提供了新的功能。AMD在高性能圖形和微處理器方面的獨特專長尤其有益,因為GCN的虛擬內存模型已被仔細定義為與x86兼容。此舉簡化了初始產品中CPU和離散GPU之間的數據移動,更重要的是,它為CPU和GPU無縫共享的單個地址空間鋪平了道路。共享而非復制數據對于性能和能效至關重要,也是AMD加速處理單元(APU)等異構系統中的關鍵元素。

    GCN命令處理器負責從驅動程序接收高級API命令,并將其映射到不同的處理管道。GCN有兩條主要管道。異步計算引擎(ACE)負責管理計算著色器,而圖形命令處理器處理圖形著色器和固定功能硬件。每個ACE都可以處理并行的命令流和圖形命令處理器可以為每種著色器類型提供單獨的命令流,從而創建大量的工作來利用GCN的多任務。

    AMD GCN架構的緩存層次結構。


    16.4.3 Intel

    下圖顯示了Intel圖形平臺上完整的功能棧所需的成分,以及每個組件的簡要說明。

    • Intel Graphics Controller:集成在第7代Intel Core處理器及更新版本中的圖形引擎硬件,用于解碼和呈現HDR內容。其顯示引擎通過HDMI和DisplayPort電纜將HDR信號傳輸到HDR顯示器。
    • Intel Graphics Driver:除上述相應的硬件外,還需要特定的驅動程序版本,始終建議獲取intel.com、PC OEM網站或通過Windows Update上發布的最新圖形驅動程序。
    • Operating System:Windows 10操作系統的適當版本,Windows 10 Fall Creators Update(RS3)是任何較新版本都適用的最低操作系統版本。
    • LSPCON:要在第7代Intel Core處理器上通過HDMI實現HDR信令,主板上必須有一個稱為LSPCON(電平移位器和協議轉換器)的額外硬件組件,是一種必須由PC制造商安裝的組件,最終用戶無法添加。注意,LSPCON僅適用于HDMI,而不適用于DisplayPort。在使用第9代Intel Core處理器或更高版本HDMI2的較新平臺中,原生支持2.0,因此不需要LSPCON支持。
    • LSPCON FW:LSPCON上需要正確版本的FW。
    • System BIOS:特別是對于超高清藍光播放,系統BIOS必須正確配置以支持Intel Software Guard Extensions(SGX)。
    • Intel CSME FW:需要Intel Management Engine(ME)固件版本才能實現必要的HW-DRM支持和HDCP2.2高級HDR視頻內容需要鏈路保護,通常包含在系統制造商擁有的系統BIOS中。
    • Intel MEI Driver:必須安裝此驅動程序,以便軟件可以與ME FW通信。
    • Application:播放HDR內容需要特定的應用程序和internet瀏覽器(如Microsoft Edge)。
    • Content:HDR視頻文件來自不同的來源,要從某些流媒體提供商(如Netflix)接收HDR內容,必須具有適當的計劃/帳戶類型。
    • HDR display:少數設備的內置顯示器上提供了稱為擴展動態范圍(EDR)的部分HDR體驗,但筆記本電腦和平板電腦的內置顯示器尚無法實現真正的HDR播放。
    • Display connector:電腦上連接到HDR顯示器的物理顯示接口(接口)可以是HDMI、DisplayPort、mini DisplayPort或USB Type-C。尋找HDCP2.2支持很重要,可將高級內容傳輸到顯示器所需的。
    • Cable/ dongle:對于支持本機HDMI或DP連接器的PC,可以直接使用適當的電纜連接到顯示器。如果PC帶有USB Type-C端口(支持Thunderbolt 3或DP Alt模式),則需要適配器或加密狗將USB Type-C轉換為HDMI2.0或DisplayPort,以及支持HDCP2,miniDP連接器也需要類似的適配器,此類適配器可從第三方供應商處獲得,如Club3D、BelkinUptab等。

    另外,Intel Graphics Media Accelerator(GMA)是Intel于2004年推出的一系列集成圖形處理器,取代了早期的Intel Extreme Graphics系列,并由Intel HD和Iris Graphics系列取代,本系列面向低成本圖形解決方案市場。該系列產品集成在主板上,圖形處理能力有限,并使用計算機的主內存而不是專用視頻內存進行存儲。它們通常出現在上網本、低價筆記本電腦和臺式電腦上,以及不需要高水平圖形功能的商務電腦上。在2007年初,大約90%的PC主板都有一個集成GPU。

    16.4.4 Qualcomm

    Freedreno是一個開源的逆向工程項目,為高通公司的Adreno圖形硬件實現了一個完全開源的驅動程序。Freedreno由MSM DRM驅動程序、xf86視頻Freedreno DDX和Mesa內部的Freedreno Gallium3D驅動程序組成。源碼地址是https://github.com/freedreno/freedreno。

    用戶空間組件可以在兩種模式下運行,要么使用msm drm/kms內核驅動程序,要么使用下游msm android樹中的msm fbdev+kgsl驅動程序。這樣做的主要目的是使在android設備上使用freedreno更加容易,特別是因為msm drm/kms驅動程序尚未對手機/平板電腦上的LCD顯示器提供完整的DSI面板支持。(即使DSI支持在msm drm/kms中就位,仍然需要為每個不同型號的LCD面板編寫面板驅動程序。)

    使用drm/kms驅動程序時,圖形堆棧看起來與任何其他開源桌面驅動程序(nouveau、radeon等)的圖形堆棧一樣:

    使用android fbdev/kgsl驅動程序時,堆棧幾乎相同,只是xf86 video freedreno中的fbmode_display模式設置代碼和libdrm_freedreno中的kgsl后端被用來代替drmmode_display和msm后端。無需重新編譯任何用戶空間組件,xf86 video freedreno和libdrm_freedreno可以確定在運行時使用什么。

    Command Processor (CP)是從ringbuffer(PM4命令流)讀取一系列渲染命令的塊,它可以設置一些寄存器值或觸發一些渲染操作,主要由Type-0(PKT0)和Type-3(PKT3)命令組成。Type-0 (PKT0)從BASE_INDEX指定的寄存器開始,將N個連續(32位)DWORD寫入N個寄存器(下圖左),Type-3(PKT3)執行IT_OPCODE操作碼指定的操作(下圖右)。

    與許多嵌入式/SoC GPU一樣,Adreno是一種基于Tile的體系架構,然而,其實現方式要簡單一些。大多數分塊器渲染小(32x32和/或64x64)分塊,硬件以某種方式對每個分塊的幾何體進行排序,通常忽略給定分塊的不可見表面。另一方面,Adreno有一個(相對)大的內核(GMEM)或片上(OCMEM)分塊緩沖區,大小從256KB到1MB不等。正在渲染的緩沖區分為“tile”或“bin”,顏色緩沖區和(如果啟用)深度/模板緩沖區可以在分塊緩沖區中容納。驅動程序完全負責每個tile/bin的繪制,以及恢復(將數據從系統內存移動到GMEM)和解析(將數據從GMEM移動到系統內存)。請注意,解析步驟可以通過多個通道用于多樣本解析。

    最簡單的方法是建立CMD以設置狀態并執行清除/繪制,忽略tile,然后在頂級命令流緩沖區(提交給內核的內容)中,執行(可選)將每個tile設置、IB(分支)解析為清除/繪制命令,然后解析:

    • 每個tile內的渲染效果與傳統IMR類似。
    • 逐tile命令:
      • 恢復——可選將內容從系統內存傳輸到tile緩沖區。
      • 設置窗口偏移和屏幕剪切。
      • IB清除/繪制渲染命令。
      • 解析——將tile緩沖區傳輸到系統內存。
    • 注意:命令流構建的順序與GPU執行的順序不同,并且恢復/解析也在清除/繪制中使用的一些GPU狀態寄存器,因此在驅動程序中需要注意在第一次清除/繪制之前將某些狀態對象標記為臟。

    以上是gallium3D驅動程序中實現的內容,當存在少量幾何體和/或廉價的頂點著色器時,不一定是一個巨大的缺點。

    上述的粗略的方法有一個缺點,即頂點著色器針對每個bin的每個頂點運行,但其實可以分為兩個過程以減少開銷。在第一個pass(“裝箱”過程)中,頂點被拆分為每個分塊箱,此信息在第二個pass中用于限制為每個箱子處理的頂點。不需要在兩個過程上使用相同的頂點著色器,裝箱過程可以使用簡化的著色器,該著色器僅計算gl_Position / gl_PointSize

    注:以下注釋適用于a3xx,但a2xx應大致相似。

    blob驅動程序為每個tile分配一個VSC_PIPE,共有八個管線,可以為一個管線分配多個tile,即可以使用四個管線來排列4x4個tile,如下所示:

    #  X, Y = upper-left tile coord of group of tiles mapped to pipe
    #  W, H = size of group in tiles, so below each pipe is mapped to
    #         a 2x2 group of tiles
    VSC_PIPE[0].CONFIG:   { X = 0 | Y = 0 | W = 2 | H = 2 }
    VSC_PIPE[0x1].CONFIG: { X = 0 | Y = 2 | W = 2 | H = 2 }
    VSC_PIPE[0x2].CONFIG: { X = 2 | Y = 0 | W = 2 | H = 2 }
    VSC_PIPE[0x3].CONFIG: { X = 2 | Y = 2 | W = 2 | H = 2 }
    

    對于每個管線,驅動程序配置管線緩沖區地址/大小( VSC_PIPE[p].DATA_ADDRESSVSC_PIPE[p].DATA_LENGTH),為gpu提供了存儲可見性流數據的位置。大小緩沖區(VSC_SIZE_ADDRESS),一個4字節 x 8個管線的緩沖區,GPU在其中存儲寫入每個管線緩沖區的數據量,在裝箱過程中用于控制將哪些頂點存儲到哪個管線。在渲染過程中,在每個tile的開始處,驅動程序將GPU配置為通過CP_SET_BIN_DATA數據包使用來自管線p的數據(即適當的緩沖區大小/地址):

    OUT_PKT3(ring, CP_SET_BIN_DATA, 2);
    OUT_RELOC(ring, pipe[p].bo, 0);  /* same value as VSC_PIPE[p].DATA_ADDRESS */
    OUT_RELOC(ring, size_addr_bo, (p * 4));  /* same value as VSC_SIZE_ADDRESS + (p * 4) */
    
    OUT_PKT0(ring, REG_A3XX_PC_VSTREAM_CONTROL, 1);
    OUT_RING(ring, A3XX_PC_VSTREAM_CONTROL_SIZE(pipe[p].config.w * pipe[p].config.h) |
            A3XX_PC_VSTREAM_CONTROL_N(n));  /* N is 0..(SIZE-1) */
    
    OUT_PKT3(ring, CP_SET_BIN, 3);
    OUT_RING(ring, 0x00000000);
    OUT_RING(ring, CP_SET_BIN_1_X1(x1) | CP_SET_BIN_1_Y1(y1));
    OUT_RING(ring, CP_SET_BIN_2_X2(x2) | CP_SET_BIN_2_Y2(y2));
    

    與大多數/所有分塊器一樣,切換渲染目標代價高昂,會觸發刷新。此外,至少對于freedreno gallium3D驅動程序,如果只渲染緩沖區的一部分,建議剪去緩沖區中不會被touch(讀或寫)的部分。gallium驅動程序可以使用此信息調整tile邊界/大小,避免不必要的恢復(將數據從系統內存拉入GMEM)或解析(從GMEM寫回系統內存)。

    對于指令集體系結構(Instruction Set Architecture,ISA),與a2xx著色器ISA不同,a3xx使用“簡單”標量指令集,但有一些技巧。編譯器需要更加了解調度和其他一些約束。每條指令為64位(qword),有7種基本指令編碼或“類別”(在某些情況下有多個子編碼)。與a2xx一樣,沒有單獨的CF vs FETCH/ALU程序。但某些類別的指令是異步運行的,需要特殊的同步來處理先讀后寫(或先讀后寫)。與a2xx不同的是,現在有完整和半(16位)寄存器,它們都沒有重疊。指令不僅針對浮點,還針對整數。7種指令編碼描述如下:

    • 類別0(cat0):通常采用零參數的流控制指令(有時帶有嵌入常量),例如:nopjumpbranch

    • 類別1(cat1):移動/轉換的變體(單個源寄存器),此類指令沒有操作碼,盡管著色器助記符因src和目標類型而異。如果src和目標類型相同,則稱為移動:

      • mov.f16f16 Rdst, Rsrc - 從同一類型src和dst移動。
      • cov.f32u16 Rdst, Rsrc - 從f32 src移動/轉換到u16 dst。
      • mova - 是mov.f16f16尋址到寄存器(a0)。(在所有情況下,src寄存器都可以是const)
    • 類別2(cat2):普通ALU指令,通常帶有2個src寄存器,但在少數情況下,第2個src編碼會被忽略:

      • add.f Rdst, Rsrc0, Rsrc1
      • and.b Rdst, Rsrc0, Rsrc1
      • floor.f Rdst, Rsrc0 - an example of cat2 which ignores the 2nd src.
    • 類別3(cat3):三個src寄存器操作,例如:

      • mad.f16 - src0 * src1 + src2
      • sel.f32 - src1 ? src0 : src2
    • 類別4(cat4):與cat1-cat3相比,復雜的單src操作需要更多的周期(可能無法預測的數量)和/或更異步:

      • rcp Rdst, Rsrc
      • log2 Rdst, Rsrc

      從cat4指令寫入的寄存器讀取的其他非cat4指令必須將(ss)位設置為與復雜alu管線同步。

    • 類別5(cat5):一般紋理樣本相關說明:

      • sam (f32)(xyzw)Rdst, Rsrc0, Rsrc1, s#0, t#0
      • isam (f32)(xyzw)Rdst, Rsrc0, Rsrc1, s#0, t#0
      • samgq (f32)(xyzw)Rdst, Rsrc0, Rsrc1, s#0, t#0
      • isam (f32)(xyzw)Rdst, Rsrc0, Rsrc1, Rsrc2
    • 類別6(cat6):將指令加載/存儲到專用/本地/全局內存、原子添加/訂閱/交換等,以及其他雜項指令。對opencl最有用。

    調度(Scheduling)上,編譯器負責在前一條指令的目標寄存器準備就緒之前考慮(指令調度)周期數。對于cat1-cat3指令,目標寄存器在三條指令之后可用,如果需要,編譯器負責插入nop指令。對于由cat4或cat5指令寫入的目標寄存器,(ss)或(sy)位可以設置為同步,因為與cat1-cat3不同,完成所需的周期數是不可預測的。特別是對于cat3指令,直到第二個周期才需要第三個src寄存器,因此,諸如DP4(點積)指令可以實現為:

    ; DP4 r0.x, r2.xyzw, r3.xyzw:
    mul.f r0.x, r2.x, r3.x
    nop
    mad.f32 r0.x, r2.y, r3.y, r0.x
    nop
    mad.f32 r0.x, r2.z, r3.z, r0.x
    nop
    mad.f32 r0.x, r2.w, r3.w, r0.x
    

    而不是需要兩個nop才能獲得前一條指令的結果,如果可能,編譯器當然可以在這些nop插槽中調度不相關的指令。寫入寄存器的指令以前是紋理樣本指令的src(WAR hazard),需要(ss)位集。

    通常,簡單的if/else構造將被展開,執行分支的所有分支,然后使用sel指令有條件地寫回從流控制角度“獲取”的分支的結果。amonst線程的發散流控制通常很昂貴(即,硬件最終必須在線程組中一次執行一個線程),因此編譯器通常會盡量避免這種情況。(目前,if/else是用gallium驅動程序中的分支實現的,僅僅是因為編譯器不夠聰明,不知道如何將其展開。)當需要分支時,可以使用cat0指令來實現它們,例如,可以通過以下方式實現if/else:

    cmps.f.eq p0.x, hr1.x, hc2.x
    br p0.x, #6
    mov.f16f16 hr1.x, hc2.x
    mov.f16f16 hr1.y, hc2.y
    mov.f16f16 hr1.z, hc2.x
    mov.f16f16 hr1.w, hc2.y
    jump #6
    (jp)nop
    mov.f16f16 hr1.x, hc2.y
    mov.f16f16 hr1.y, hc2.x
    mov.f16f16 hr1.z, hc2.x
    mov.f16f16 hr1.w, hc2.y
    (jp)nop
    

    請注意,分支目標指令上設置了(jp)(跳轉目標)標志,可能有助于線程調度器找出潛在的聚合點,跳轉目標不必是nop。分支可以是向前(正)或向后(負)立即偏移。

    分組通道結束后,沒有更多要插入或刪除的指令。從深度通道創建的深度排序列表中最深的節點開始調度每個基本塊,遞歸地嘗試在每個指令的源指令加上延遲槽之后調度每個指令,根據需要插入NOP。

    在指令中使用const src參數有一些限制,在某些情況下,編譯器需要將const移動到GPR中。已知的限制包括:

    • cat2最多可以使用一個常量src(但可以在任意位置)。
    • cat3不能將常量src作為第二個參數(src1)。
    • cat4不能接受常量src。

    16.4.5 其它

    此外,還有其它平臺的驅動:

    • Vidix:是一種適用于類Unix操作系統的便攜式編程接口,它允許在用戶空間中運行的視頻卡驅動程序通過X Window系統的直接圖形訪問擴展直接訪問幀緩沖區。

    • MPLAB:MPLAB Harmony Graphics Suite是MPLAB生態系統的擴展,用于為32位微芯片設備創建嵌入式圖形固件解決方案。

    • MiniGLX:是一種應用程序編程接口規范,有助于在沒有窗口系統的系統上進行OpenGL渲染,例如,沒有X窗口系統的Linux或沒有窗口系統的嵌入式系統。該接口是GLX接口的子集,加上一組最小的Xlib類函數。


    16.5 圖形驅動應用

    16.5.1 視頻與合成

    在運行GFX/視頻播放用例(應用程序的視頻流類型)時,查看影響英特爾體系結構下UI體驗的特定穩定性問題,行為是凍結一個UI,然后是一個黑屏,然后是系統重新啟動(當然是在一段隨機的時間間隔之后)。

    如果3D客戶端應用程序“掛起”GPU,則GPU進程可能會被終止,然后GPU會完全重置。對于復雜的用例,如視頻解碼,許多幀/對象當前處于運行狀態,因此終止GPU進程并重置GPU會導致不受歡迎的效果。

    建議的解決方案:超時檢測和恢復(Timeout Detection & Recovery,TDR)。Intel GPU的新功能(上游為wip),允許應用程序在單個批處理緩沖區上啟用掛起檢測,從而提高穩定性和魯棒性。超時檢測和恢復(TDR)允許獨立重置GPU中的不同引擎(而不是完全重置GPU)。一般來說,這些實現在i915驅動程序中引入了一個新的IRQ處理程序,以及在gpu的環形緩沖區中發出的批處理緩沖區的啟動指令之前和之后引入了兩個新的gpu命令指令。TDR的步驟如下:

    建議的解決方案:

    1、UMD媒體驅動程序在發送批緩沖區后啟動定時器。

    2、計時器過期后,檢測到媒體引擎處于掛起狀態。

    3、GPU驅動程序僅重置受影響的媒體引擎。

    4、由于UMD媒體驅動程序知道提交錯誤批次的時間,因此可以在媒體驅動程序從重置中恢復的時間內采取措施。

    整個機制通過任意閾值工作,該閾值可以通過ioctl從應用程序設置。但閾值不能太低,否則會產生太多誤報。

    合成器(compositor)如何受益?結合下圖加以回答:

    1、合成器的基本任務是生成幀。

    2、過去,當我們檢測到GPU掛起時,合成器恢復(屏幕凍結、綠色或黑色屏幕或系統重新啟動)為時已晚。

    3、視頻客戶端應用程序現在可以早期確定“任務”是否導致媒體引擎崩潰,如果是,則向合成器標記以顯示當前幀,同時媒體引擎從重置中返回。

    16.5.2 Rocksolid

    Rocksolid最初是GPU基準產品的引擎,后來發展成為一個獨立的產品。除了客戶需求之外,該開發仍然與基準開發緊密相關。輕量級渲染/計算引擎主要針對非游戲用途,從小型嵌入式系統擴展到現代桌面級硬件。它不是一個成熟的游戲引擎,但它的開銷比大型現代游戲引擎低得多,也更容易定制,更穩定,并且可以通過獲得安全認證。下圖是Rocksolid引擎的架構圖,由此可知,它可以直接訪問設備驅動,從而提升性能。

    圖形管線是一種非常好的運行方式,例如圖像處理任務。在許多硬件中,如果問題自然映射到全屏光柵化過程,則將其作為圖形管道而不是計算管道運行會更快。在一個工業客戶案例中,Rocksolid被用于提供GPU加速的圖像處理管道。與原始OpenCL版本相比,目標硬件的速度快了好幾倍。

    在OpenGL中,Rocksolid引擎只是在拓撲排序中運行記錄的節點命令列表。在Vulkan中,每個使用的命令隊列都有一個提交線程。目前,默認設置只是一個命令隊列,所以只有一個提交線程。提交線程連續運行。當提交線程仍在推送命令時,程序的其余部分可以準備下一幀。Vulkan的CPU可見資源由簡單的循環圍欄系統保護。

    16.5.3 I/O驅動

    將應用程序的輸入/輸出請求轉換為設備的低級命令,并將其發送給設備控制器,獲取輸入/輸出設備的響應并將其發送到應用程序。

    輸入/輸出系統的各層以及各層的主要功能。

    如何在硬件中訪問IO呢?步驟如下:

    • 操作系統需要向設備控制器發送/接收命令和控制以完成輸入/輸出。
    • 設備控制器有一個或多個用于控制和數據的寄存器。
    • 處理器通過讀/寫這些寄存器與控制器通信。
    • 如何尋址這些寄存器?
      • 基于內存的I/O。
      • 基于端口的輸入/輸出。
      • 混合輸入/輸出。

    內存映射/基于端口/混合的IO的方式如下圖:

    (a)特殊CPU指令(輸入/輸出)。(b)內存映射:為硬件輸入/輸出寄存器保留內存區域。標準內存指令會更新它們。(c)混合:一些控制器映射到內存,一些使用I/O指令。

    單總線和雙總線IO。(a)內存映射I/O只有一個地址空間,內存映射I/O更易于實現和使用。幀緩沖區或類似設備更適合于內存映射的I/O。(b)基于端口的I/O有兩個地址空間:一個用于內存,一個用于端口。雙總線允許并行讀/寫數據和設備。

    單總線和雙總線更詳細的對比圖如下:


    總線:組件(包括CPU)之間的互連,可以連接多個設備:

    端口:僅插入一個輸入/輸出設備的接口:

    設備控制器:將物理設備連接到系統總線/端口:

    每個設備都有一個設備控制器和一個設備驅動程序來與操作系統通信,設備驅動程序是可以插入操作系統以處理特定設備的軟件模塊,設備控制器用作設備和設備驅動程序之間的接口,設備控制器可以處理多個設備。作為一個接口,它的主要任務是將串行位流轉換為字節塊,并根據需要執行糾錯。

    IO端口寄存器有狀態寄存器(由host讀取)、命令寄存器(由host寫入)、寄存器中的數據(由host讀取以獲取輸入)、數據輸出寄存器(由host寫入以發送輸出)。

    直接內存訪問(Direct Memory Access,DMA):對于進行大型傳輸的設備,如磁盤驅動器,使用昂貴的通用處理器來監視狀態位并將數據一次輸入1字節的控制器寄存器,似乎是一種浪費——這一過程稱為編程輸入/輸出。基于中斷的I/O不是一種補救方法,因為每個字節都會創建一個到中斷處理程序例程的上下文開關。在基于輪詢和基于中斷的I/O中,所有字節都需要通過CPU,并且輸入/輸出設備<->CPU<->內存有很多開銷。如果我們可以將這個平凡的任務卸載到一個特殊用途的處理器上,該處理器可以將數據從輸入/輸出設備直接移動到內存中,那就太好了!這就是直接內存訪問(DMA)控制器。

    要啟動DMA傳輸,host將DMA命令塊寫入內存。指向傳輸源的指針,指向傳輸目標的指針,以及要傳輸的字節數的計數。CPU將此命令塊的地址寫入DMA控制器,然后繼續其他工作。DMA控制器繼續直接操作內存總線,在總線上放置地址以執行傳輸,而無需主CPU的幫助。簡單的DMA控制器是PC中的標準組件,PC的總線主控輸入/輸出板通常包含自己的高速DMA硬件。使用DMA的IO示例:

    /* Code executed when the print system call is made */
    copyFromUser(buffer, p, count); 
    setupDMAController();
    scheduler();
    
    /* Interrupt Service Routine Procedure for the printer */
    acknowledgeInterrupt();
    unblockUser();
    returnFromInterrupt();
    

    請注意,中斷是每個I/O任務生成一次,而不是每個字節生成一次(在基于中斷的I/O情況下)。

    IO硬件接口:設備驅動程序被告知將磁盤數據傳輸到地址X處的緩沖區,設備驅動程序告訴磁盤控制器將C字節從磁盤傳輸到地址X處的緩沖區,磁盤控制器啟動DMA傳輸,磁盤控制器將每個字節發送到DMA控制器,DMA控制器將字節傳輸到緩沖區X,遞增內存地址,遞減C直到0,當C==0時,DMA中斷CPU以完成信號傳輸。

    應用程序IO接口如下圖所示:

    連接到計算機的每個輸入/輸出設備都需要一些特定于設備的代碼來控制它。設備制造商編寫,每個操作系統都需要自己的設備驅動程序,每個設備驅動程序都支持特定類型或類別的輸入/輸出設備。鼠標驅動程序可以支持不同類型的鼠標,但不能用于網絡攝像頭。操作系統定義了驅動程序的功能以及它如何與操作系統的其余部分交互。設備驅動程序具有多個功能,要接受來自其上方獨立于設備的軟件的抽象讀寫請求,并確保執行這些請求,設備初始化,管理is電源需求和日志事件。

    Microsoft Windows使用文件系統上的設備快捷方式來尋址設備,設備訪問API為應用程序程序員提供了一個接口,以檢查設備并與之交互,Windows設備框架為設備驅動程序開發提供了用戶和內核界面。輸入/輸出分類(操作系統視角)有:

    • 字符流和塊。
    • 順序訪問與隨機訪問。設備驅動程序允許查找設備中的偏移量。
    • 同步與異步。設備驅動程序上的I/O操作與設備控制器上的I/O完成同步,異步I/O更早返回,稍后報告成功/失敗。
    • 緩沖和直接。報告的操作結果在緩沖區或設備控制器上完成。
    • 共享或專用。每個設備實例上的I/O是互斥的。(即打印機)
    • 只讀、只寫、讀寫。

    內核提供了許多與I/O相關的服務:調度、緩沖、緩存、池化、設備保留及錯誤處理,基于硬件和設備驅動程序基礎架構構建。

    IO調度用來調度一組I/O請求,意味著確定執行它們的良好順序。操作系統開發人員通過維護每個設備的請求隊列來實現調度,當應用程序發出阻塞I/O系統調用時,該請求將被置于該設備的隊列中。I/O調度器重新排列隊列的順序,以提高總體系統效率和應用程序的平均響應時間。輸入/輸出通常很慢,一些設備的物理特性需要優化,例如硬盤——由磁頭移動和旋轉引起的機械裝置和延遲。如果在FIFO策略中執行輸入/輸出,機械之字形運動可能會否決輸入/輸出操作。I/O調度獲取設備上的一組I/O請求,并確定在設備上執行請求的最佳順序和時間。當多個任務競爭要處理的I/O請求時,調度變得復雜。

    塊設備操作與虛擬內存和分頁緊密耦合,一些幀用作頁面緩存,并將塊設備的數據保存在系統中。塊設備中的輸入/輸出:搜索塊是否已在頁緩存中(物理內存中):如果找到現有幀的讀/寫緩沖區,否則,分配一個幀,將設備塊讀取到幀中,將幀標記為緩存設備塊對 從此幀讀取/寫入緩沖區。臟頁定期寫入塊設備,顯著加快I/O操作,尤其是文件系統元數據操作。

    頁面緩存思想還與內存映射的I/O相結合,mmap()文件采用類似的機制。進程的虛擬內存映射作為文件(而不是塊設備)備份的頁,更改會在內存上更新,緩存的幀會定期在磁盤上強制執行。VM系統跟蹤頁面和文件緩存以及其他(常駐和免費)頁面的幀,VM系統根據系統的內存狀態調整文件和設備緩存的大小。

    緩沖區是在兩個設備之間或設備與應用程序之間傳輸數據時存儲數據的存儲區域。進行緩沖有三個原因:

    • 處理數據流的生產者和消費者之間的速度不匹配。通過適配器接收文件以存儲在硬盤上。

      (a) 無緩沖輸入。(b) 用戶空間中的緩沖。(c)在內核中進行緩沖,然后復制到用戶空間。(d) 內核中的雙緩沖。

    • 在具有不同數據傳輸大小的設備之間進行調整。網絡:消息通常在發送和接收過程中被分割。

      聯網可能涉及一個數據包的多個副本。

    • 支持應用程序I/O的復制語義。應用程序調用write()系統調用,提供指向緩沖區的指針和指定要寫入的字節數的整數。系統調用返回后,如果應用程序更改緩沖區的內容,會發生什么情況?在處理write()系統調用時,操作系統會將應用程序數據復制到內核緩沖區,然后再將控制權返回給應用程序。磁盤寫入是從內核緩沖區執行的,因此對應用程序緩沖區的后續更改不會產生任何影響。

    緩存在I/O級別完成,以提高I/O效率。緩沖區和緩存之間的區別在于,緩沖區可能只保存數據項的現有副本,而緩存根據定義,只保存位于其他位置的項的更快存儲上的副本。緩存和緩沖是不同的功能,但有時一個內存區域可以用于這兩個目的。例如,為了保留拷貝語義并實現磁盤I/O的高效調度,操作系統使用主存中的緩沖區來保存磁盤數據。

    輸入/輸出軟件通常分為四層,每個層都有一個定義良好的接口。

    下面是有(右)無(左)標準驅動接口的對比圖:

    16.5.4 UE圖形驅動

    雖然原則上應用層不應該關心驅動層和GPU的細節,但往往事與愿違,眾多的GPU、系統、圖形API、版本造就了眾多的驅動程序版本,它們之間可能存在一些奇奇怪怪的問題,而驅動程序有著很長的供應鏈路,修復問題的周期往往比較漫長。作為應用程序開發者,肯定不能坐以待斃,需主動解決或規避。

    想到多年前,博主在工作中遇到一個奇怪的bug:在某個品牌GPU的某個版本的驅動程序采樣的紋理顏色有問題。后來經過一周艱苦調試才發現,是那個驅動版本實現的texture.sample的坐標沒有偏移到紋素中心,導致LUT紋理顏色出現巨大的偏差。解決的方法也很簡單:針對那個驅動版本,自定義實現了texture.sample的shader代碼。

    UE提供了有限的接口和類型,為我們提供了一些信息,從而可以讀取GPU或驅動的信息。相關的主要接口:

    // GenericPlatformDriver.h
    
    // 視頻驅動細節
    struct FGPUDriverInfo
    {
        uint32 VendorId; // DirectX供應商ID,0如果未設置,請使用以下函數設置/獲取
        FString DeviceDescription; // e.g. "NVIDIA GeForce GTX 680" or "AMD Radeon R9 200 / HD 7900 Series"
        FString ProviderName; // e.g. "NVIDIA" or "Advanced Micro Devices, Inc."
        FString InternalDriverVersion; // e.g. "15.200.1062.1004"(AMD), "9.18.13.4788"(NVIDIA)
        FString UserDriverVersion; // e.g. "Catalyst 15.7.1"(AMD) or "Crimson 15.7.1"(AMD) or "347.88"(NVIDIA)
        FString DriverDate; // e.g. 3-13-2015
        FString RHIName; // e.g. D3D11, D3D12
    
        FGPUDriverInfo();
        
        bool IsValid() const;
        FString GetUnifiedDriverVersion() const;
        
        bool IsAMD() const { return VendorId == 0x1002; }
        bool IsIntel() const { return VendorId == 0x8086; }
        bool IsNVIDIA() const { return VendorId == 0x10DE; }
        
        (...)
    };
    
    // GPU硬件信息
    struct FGPUHardware
    {
        // 驅動信息
        const FGPUDriverInfo DriverInfo;
    
        FGPUHardware(const FGPUDriverInfo InDriverInfo);
        
        FString GetSuggestedDriverVersion(const FString& InRHIName) const;
        FBlackListEntry FindDriverBlacklistEntry() const;
        bool IsLatestBlacklisted() const;
        const TCHAR* GetVendorSectionName() const;
        
        (...)
    };
    
    
    // GenericPlatformMisc.h
    
    struct CORE_API FGenericPlatformMisc
    {
        // 獲取GPU驅動信息
        static struct FGPUDriverInfo GetGPUDriverInfo(const FString& DeviceDescription);
        
        (...)
    };
    

    有了以上接口,就可以方便地在渲染層、游戲邏輯層獲取驅動信息以執行針對性的操作。

    除了GPU驅動,還可以訪問圖形API的驅動,常出現在各個圖形API的RHI實現中,例如Android Vulkan:

    // AndroidPlatformMisc.cpp
    
    bool FAndroidMisc::HasVulkanDriverSupport()
    {
        (...)
        
        // this version does not check for VulkanRHI or disabled by cvars!
        if (VulkanSupport == EDeviceVulkanSupportStatus::Uninitialized)
        {
            // assume no
            VulkanSupport = EDeviceVulkanSupportStatus::NotSupported;
            VulkanVersionString = TEXT("0.0.0");
    
            // check for libvulkan.so
            void* VulkanLib = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
            if (VulkanLib != nullptr)
            {
                UE_LOG(LogAndroid, Log, TEXT("Vulkan library detected, checking for available driver"));
    
                // if Nougat, we can check the Vulkan version
                if (FAndroidMisc::GetAndroidBuildVersion() >= 24)
                {
                    extern int32 AndroidThunkCpp_GetMetaDataInt(const FString& Key);
                    int32 VulkanVersion = AndroidThunkCpp_GetMetaDataInt(TEXT("android.hardware.vulkan.version"));
                    if (VulkanVersion >= UE_VK_API_VERSION)
                    {
                        // final check, try initializing the instance
                        VulkanSupport = AttemptVulkanInit(VulkanLib);
                    }
                }
                else
                {
                    // otherwise, we need to try initializing the instance
                    VulkanSupport = AttemptVulkanInit(VulkanLib);
                }
    
                dlclose(VulkanLib);
    
                if (VulkanSupport == EDeviceVulkanSupportStatus::Supported)
                {
                    UE_LOG(LogAndroid, Log, TEXT("VulkanRHI is available, Vulkan capable device detected."));
                    return true;
                }
                else
                {
                    UE_LOG(LogAndroid, Log, TEXT("Vulkan driver NOT available."));
                }
            }
            else
            {
                UE_LOG(LogAndroid, Log, TEXT("Vulkan library NOT detected."));
            }
        }
        
        return VulkanSupport == EDeviceVulkanSupportStatus::Supported;
    }
    

    16.6 本篇總結

    本篇主要闡述了圖形渲染體系的驅動部分,包含它的概念、技術、架構、機制和相關的硬件部件。

    在PC上,開發者可以要求用戶更新其驅動程序,這樣做相對容易。在移動設備上,驅動需要在多個階段獲得認證,從硬件供應商到客戶的鏈條很長,這樣做既昂貴又緩慢,錯誤修復和新功能可能需要數月時間,意味著bug修復可能需要很長時間才能進入用戶的手中。在較舊/低端設備上,它們可能永遠不會到來。目前正在努力改進這一點,但如果看到驅動程序錯誤,行業從業者準備解決它。這一情況正在好轉(但進程緩慢)。

    說到驅動程序bug,在移動設備上研究Vulkan驅動程序的團隊通常沒有在PC上那么大,有很多GPU的嵌入式變體需要測試。意味著驅動的質量常常有點落后。再加上發布修復程序的延遲,看看它是否有效或引入其他問題,驅動程序質量成為一個問題就不足為奇了。隨著一致性測試的改進,驅動程序的測試變得更好,Khronos的硬件供應商非常清楚這是一個高度優先事項。需要行業從業者齊心協力改善這一點(CTS)。

    隨著現代圖形API的到來,驅動不再進行隱式的優化,可預測的性能是可預測的低!使用新API啟動新項目的開發人員具有優勢(下圖),新的圖形API給大型軟件安裝群帶來了更多的問題。構建渲染流,以便以正確的順序獲得所需的信息,在開始時要比改裝容易得多。最重要的是,可以根據舊的API執行的驅動程序優化來編寫引擎,以獲得良好的性能。現在,軟件開發人員擁有了控制權,使得性能可以預測,并確保應用了正確的優化,但也確實意味著由開發人員來確保這些優化存在。

    img
    img

    DirectX11驅動程序(上)和DirectX12應用程序(下)執行的工作對比圖。



    特別說明

    • 感謝所有參考文獻的作者,部分圖片來自參考文獻和網絡,侵刪。
    • 本系列文章為筆者原創,只發表在博客園上,歡迎分享本文鏈接,但未經同意,不允許轉載
    • 系列文章,未完待續,完整目錄請戳內容綱目
    • 系列文章,未完待續,完整目錄請戳內容綱目
    • 系列文章,未完待續,完整目錄請戳內容綱目

    參考文獻

    posted @ 2022-06-25 19:21  0向往0  閱讀(992)  評論(1編輯  收藏  舉報
    国产美女a做受大片观看