? ? 前言
? GPU (Graphics Processing Unit)是一種專門在個人電腦、工作站、游戲機和一些移動設備(如平板電腦、智能手機等)上做圖形相關運算工作的微處理器。隨著AI興起,適合并行運算的 GPU 也被廣泛應用于訓練和推理, 大量的服務器開始搭載 GPU 做計算任務。當前 GPU 包含多個引擎,包含渲染,計算,編解碼,顯示, DMA(Designated Market Area)等多個硬件模塊。每個硬件對應一個或者多個引擎。本文主要介紹 render 引擎, 從 GPU 渲染的硬件單元,到用戶態頂點,命令等數據下發給 GPU 硬件執行過程等方面進行詳細介紹,幫助大家更好地理解 render 引擎工作流程。(特別聲明:本文主要以 Intel GPU 為參考介紹)
名詞解析
3D Pipeline: 3D 管道是一組以流水線方式排列的固定功能單元,它通過固定函數單元和 EU 線程來處理與 3D 相關的命令。FF(fixed fuction): 具有固定功能的硬件。FF units: 在 GPU 3D Pipeline 中一個固定的功能單元。
FFID: Unique identifier for a fixed function unit.
CS(Command Streamer): 固定功能單元,解析驅動寫入到 ring buffer 里的命令,發送到 3D Pipeline 下一級。
VF(Vertex Fetcher): 3D Pipeline 中第一個 FF 固定功能單元,讀取內存中的頂點數據,處理后傳遞給 3D Pipeline 下一個階段 VS。EU(Excution Unit): 多線程的執行單元,每個 EU 都是一個處理器。
TD(Thread Dispatcher): 功能單元,用來仲裁來自固定函數單元的線程啟動請求并在 EU 上實例化線程的功能單元。
Render 引擎介紹
Intel Render 引擎有兩種工作模式:一種是 3D 渲染,另一種是 media(編解碼相關)模式(計算 GPGPU 模式和media 是同一個)。驅動通過 PIPELINE_SELECT 命令選擇 3D/Media 模式:
// 在 mesa 代碼中 compute 執行 、 emit_pipeline_select(batch, GPGPU); //在 render 中執行 emit_pipeline_select(batch, _3D);無論哪種工作方式都是用戶態驅動和內核態驅動將命令寫入到 ring buffer,然后提交給硬件,硬件通過?PIPE_SELECT 命令選擇使用指定的 pipeline,然后將用戶命令和 buffer 發送硬件。硬件執行命令流程如下圖: ? ? ?
?
3D pipeline 流程圖
上圖中黃色的部分,是通過 EU(可以編程的計算單元)實現,在 NVIDIA 中,該硬件類似的功能叫做 CUDA(Compute Unified Device Architecture)。藍色的部分是通過固定的硬件來實現。所以藍色部分叫做 FF 固定函數單元。
?
GPU render 引擎結構
一圖勝千言,從上圖中可以看到渲染和 media 功能是由 CS、FF 固定函數單元配合 Slice 中 EU、采樣器、SliceCommon 硬件等共同完成。上圖只展示了一個 slice,真實的 GPU 包含不同數量的 slice。
硬件介紹和分析
?
Command Streamer
GPU 執行相關的 pipeline 操作需要 CPU 下發命令,CPU 寫入命令到一個 buffer 里,該 buffer 一般稱為 batch buffer。在驅動里把該 buffer 轉換成 ring buffer 或者 batch buffer。
GPU render 相關的命令大體分為以下類型:
memory interface: 內存接口命令,對 memory 進行操作的命令。
3D state: 設置 3D pipeline 狀態機的命令,例如頂點 surface state 狀態下發到 GPU。
pipe Control: 用來設置同步或者并行執行的操作。
3D Primitive 圖元裝配有關的命令。
命令下發后就需要硬件去解析命令。從上圖 3D 引擎的結構中能看到,首先是 Command Streamer 硬件讀取 ring buffer 數據中的命令。Command Streamer 是各種引擎的主要接口。每個引擎都有自己的 Command Streamer。
固定函數單元
? 圖 Generic 3D FF Unit Block Diagram FF 函數單元的一個主要作用是管理對頂點/像素數據執行大部分處理的 EU 線程。在一般意義上,所包括的關鍵功能是:
Bypass Mode
URB Entry Management
Thread Initiation Management
Thread Request Data Generation ? Thread Control Information Generation ? Thread Payload Header Generation ? Thread Payload Data Generation
Thread Output Handling
URB Entry Readback
Statistics Gathering
FF 單元有個很重要的功能是完成 Thread Dispatching (thread 調度器)任務, 根據 VS/GS 等等 stage ?需求發起不同的 EU thread。FF 單元常用的命令大部分都和線程啟動初始化相關。
上面的命令都是 thread init 需要,后面是常量包含到 URB thread 運行時候需要讀取的數據。
?
EU(Excution Unit)
EU 的官方解釋為:
An EU is a multi-threaded processor within the multi-processor system. ?Each EU is a fully capable processor containing instruction fetch and ?decode, register files, source operand swizzle and SIMD ALU, etc. An EU is ?also referred to as a core
從官方解釋也能看出 EU 是個多線程的執行單元,在最近的 gen11/gen12中,每個 EU 都是具有七個線程的多線程 (SMT)。主計算單元由支持 SIMD 的 ALU/FPU 組成。每個硬件線程都有 128 個 32 位寬的通用寄存器 (GRF)。OpenGL 中的 shader 和 OpenCL 中的 kernel 做計算都是由 EU 單元完成。
常用到基本概念
Thread: An instance of a kernel program executed on an EU. The life cycle for a thread starts from the executing the first instruction after being dispatched from Thread Dispatcher to an EU to the execution of the last instruction – a send instruction with EOT that signals the thread termination. Threads in GEN system may be independent from each other or communicate with each other through Message Gateway share function
Thread Dispatcher : Functional unit that arbitrates thread initiation requests from Fixed Functions units and instantiates the threads on EUs.
Thread Payload :Prior to a thread starting execution, some amount of data will be pre-loaded in to the thread’s GRF (starting at r0). This data is typically a combination of control information provided by the spawning entity (FF Unit) and data read from the URB
Thread Identifier : The field within a thread state register (SR0) that identifies which thread slots on an EU a thread occupies. A thread can be uniquely identified by the EUID and TID.
Thread Spawner : The second and the last fixed function stage of the media pipeline that initiates new threads on behalf of generic/media processing.
Unified Return Buffer(URB):The on-chip memory managed/shared by GEN Fixed Functions in order for a thread to return data that will be consumed either by a Fixed Function or other threads.
URB Entry: A logical entity stored in the URB (such as a vertex), referenced via a URB Handle.
URB Entry Allocation Size: Number of URB entries allocated to a Fixed Function unit.
URB Fence:Virtual, movable boundaries between the URB regions owned by each FF unit.
EU 的結構
GRF(General Register File)通用寄存器組。ARF(Architecture Register File)體系結構寄存器組。
常用的 ARF 寄存器:
類似 arm 體系結構除了 x0 到 x30 通用寄存器,還有一些 sp,ret ,pc,PSTATE 等特殊寄存器。
FPU 就是常說的 ALU 算術邏輯運算單元,使用 SIMD 指令實現。包含以下指令:
EU 的寄存器數據存儲方式
EU 寄存器不同的數據存儲方式 AOS 和 SOA:
根據 GPU 文檔介紹:
頂點著色器和幾何著色器使用 AOS 方式存儲,同時使用 SIMD4x2 and SIMD4 modes 模式操作;
像素著色器使用 SOA 方式 使用 SIMD8/SIMD16 方式操作數據;
在 media 中主要使用 SOA 方式排列,偶爾也有 AOS
SIMDmxn 代表操作向量的大小,n 代表同時有幾個在同時進行操作。SIMD4 代表上圖中 x、y、z、w 同時操作。
一個 EU 例子:
?
add dst.xyz src0.yxzw src1.zwxy
?
用戶態對 EU 的使用
OpenGL 程序可以編寫 shader(glsl)然后通過 GPU 編譯器解析成 GPU 指令,并由 GPU 執行。OpenCL 中可以編寫 kernel(OpenCL定義的Kernel語言,基于C99擴展)程序,經過編譯到 GPU 上執行。無論 shader 還是 kernel 都在執行時才進行編譯。根據不同的 GPU 編譯成特定的二進制。在 mesa 中大體過程如下:
在 mesa 中 OpenCL 和 OpenGL GPU 的編譯器都在 mesa/src/目錄下,編譯完成后根據 vulkan 或者 openGL 分別調用 upload_blorp_shader/iris_blorp_upload_shader 拷貝到 res bo 中(batchbuffer),當 context 被調度執行 會讀取 batchbuffer 數據,GPU 加載到 EU 執行。
EU 中的 Messages
EU 與共享函數之間以及固定功能管道(不被認為是“子系統”的一部分)以及 EU 與 EU 之間的通信是通過信息包完成的 。
通過發送指令請求消息傳輸,Messages 分為兩大類型:
Message Payload:寫入 MRF (Message Register ?File )寄存器的內容,發送有效的信息。
Associated ("sideband") information :指令中包含的其他信息 具體如下:
Message Descriptor. Specified with the send instruction. Included in the message ?descriptor is control and routing information such as the target function ID, message ?payload length, response length, etc. Additional information provided by the send instruction, e.g., the starting destination ?register number, the execution mask (EMASK), etc. ? ?A small subset of Thread State, such as the Thread ID, EUID, etc.
備注:MRF(Message Register ?File ):只寫寄存器,用于在發送前組裝消息,并作為發送指令的操作數。每個線程都有專有的 MRF 寄存器組由 16 個寄存器組成,每個寄存器 256 位寬。每個寄存器都有一個專門的狀態位用來標記當前寄存器是否是正在發送消息的一部分,即標記是否在使用。對于大多數 Message ?第一個寄存器是報文頭,其他的寄存器是 data。
一個 Message 的生命周期分為四個階段:(1)創建 (2)發送 (3)處理 (4)回寫。
什么是共享函數?
共享函數是為 EU 提供專門補充功能的硬件單元。一個共享函數是在每個 EU 原有基礎功能上的增強,獨立的硬件加速。共享函數是作為 EU 以外的獨立實體運行,并在各 EU 之間共享。
共享函數的調用是通過稱為消息的通信機制實現的。消息由一系列 MRF(Message Register ?File)定義,這些寄存器保存消息操作數、目標共享函數 ID、需要操作特定函數的編碼、目標通用任何回寫響應指向的 GRF (General Register File) 。消息通過發送指令在軟件控制下發送到共享函數。最常見的共享函數: ? Extended Math function ? Sampling Engine function ? DataPort function ? Message Gateway function ? Unified Return Buffer (URB) ? Thread Spawner (TS)
? Null function
上圖為 Send 主要發送 Messages 消息到共享函數,Send 用來發送通用指令,處于 messsage 的發送階段,message 指令類型: ? Mem stores/reads are messages ? Mem scatter/gathers are messages ? Texture Sampling is a message with u,v,w coordinates per SIMD lane
? Messages used for synchronization, atomic operations, fences etc.
Unified Return Buffer (URB)
The on-chip memory managed/shared by GEN Fixed Functions in order for a thread to return data that will be consumed either by a Fixed Function or other threads.
統一返回緩沖區(URB)是一種通用的緩沖區,用于在不同的線程之間發送數據,在某些情況下,還可以在線程和固定函數單元之間發送數據(反之亦然)。一個線程 a 通過發送消息來獲取 URB。URB 通常以行為顆粒度進行讀寫。
URB 的空間布局:
URB entry
URB entry 是 URB 中的一個邏輯實體,由 URB handle 引用,并由若干個連續的行組成。
Constant URB Entries (CURBEs)
EU 中的 thread 在計算時可能會遇到程序里的一些常量 ,EU 可以通過 Dataport 讀取內存中的常量數值,但是這會影響性能。為了加速讀取常量的速度,GPU 可以把內存數據寫入到 URB 中。這樣常量可以放入到 URB 中,提高 thread 讀取常量的速度。將常量寫入線程有效負載的機制叫做 CURBEs。CURBE 是 一個由 command stream 控制的 URB entry,CURBE 是一個特殊的 URB entry。CS 解析到 CONSTANT_BUFFER 命令后會從內存中讀取常量數據并寫入到 CURBE。
URB 分配
Engine command streamer 通過解析命令來管理 GPU 中的 URB
3D Pipeline 上每個階段都需要使用 URB, 各個階段 VF ,VS 等都需要分配自己的 URB。FF 單元將頂點、vertex ?patch 、constant data 從內存寫入到 URB entries 中,可以通過 URB_FENCE 命令用來完成這個任務。每個 stage 都有一個 fence 指針,該指針指向自己使用的 URB 結束位置。上一個 stage fence 指針和自己 fence 指針中間的位置就是使用的 URB 區間。
圖 URB Allocation - 3D Pipeline
URB 的讀寫
URB 寫入:
CS 會將常用數據寫入到 URB 中 Constant URB Entries
Media pipeline Video Front End (VFE) 固定單元將 thread 預加載數據寫入到 URB entry 中
3D pipeline Vertex Fetch (VF) 固定函數單元寫頂點數據到 URB entry 中
Thread 寫數據到到 URB entry
URB 讀取:
線程調度器(Thread Dispatcher)是 URB 讀取的主要來源。作為生成線程的一部分,管道固定函數為線程調度器提供了許多 URB 句柄、讀取偏移量和長度。線程調度器從 URB 中獲取指定的數據,并提供預加載到 GRF 寄存器中。
3D 管道的幾何著色器(GS)和 Clipper(CLIP)固定函數單元可以讀取 URB entry 的選定部分,以提取管道所需的頂點數據。
windows(WM)FF 單元從條帶/風扇單元編寫的 URB 條目中讀取回深度系數。
URB ?讀寫通過 URB 共享函數實現,URB 共享函數通過 URB_WRITE 和 URB_READ message 類型。
URB Entry
URB Entry 分配從 ?URBStartAddress 指定的地址開始。
分配的大小由 NumberOfURBEntries 和 URBEntryAllocationSize 決定。
存儲頂點的 entry 被稱為 Vertex URB Entry,一般來說,頂點數據存儲在 URB 中 vertex URB entry(VUEs)中,由 CLIP 線程處理,僅通過 VUE 句柄間接引用。因此,對于大部分頂點數據的內容/格式不暴露于 3D 管道硬件——FF 單元通常只知道數據的句柄和大小。
發起 EU thread
1. Thread Dispatching
當 3D 和 media pipeline 向 subsystem 發送線程啟動請求時,線程調度器(Thread Dispatching )接收這些請求。調度程序執行諸如在并發程序之間進行仲裁等任務,將請求的線程分配給 EU 上的硬件線程,在多個線程中分配每個 EU 中的寄存器空間,并使用來自 FF 單元的數據初始化一個線程中的寄存器。
FF 單元會給 thread Dispatching 發送請求,FF 單元收集 thread 啟動需要的信息,寫入到 URB 中。Thread 啟動的時候從 URB 加載到 GRF(General Register File)寄存器。
2. Thread Spawner
Media pipeline 有兩個固定函數單元,Video Front ?End (VFE) unit and Thread Spawner (TS) unit。
TS 是 media 功能時候發起一個通用的線程。VFE 解析命令流,然后將線程啟動預加載的數據填寫到 URB,TS 發起一個新的線程。TS 是 media pipeline 中唯一一個與線程調度器(Thread Dispatching )聯系的單元。
3. Thread 啟動需要信息
FF 單元確定可以請求一個線程,它就必須收集向線程調度程序提交線程發起所需的所有信息。這些信息可分為幾個類別: ? Thread Control Information: FF 單元會給 thread Dispatcher 發送 thread 運行的控制信息,包括 Kernel Start ?Pointer,Binding ?Table Entry ?Count 等等。這些信息不是直接發送到 thread payload 中
? Thread Payload Header: GRF 是 EU 的通用寄存器,thread 運行之前需呀講一些數據預加載到 GRF 中(GRF r0 寄存器寄存器開始)這些數據來自 FF 單元。
此數據分為兩部分:固定頭部數據和 R+ 頭部數據: 固定頭部數據:包含了 FF 單元從 FF pipe 獲取到的信息,是所有 thread 都用的信息,這里就包含后面。sampler 會用到的 surface state, sampler state 指針等等。 R+?頭部數據?: 包含了 R0 R1 .. ?包含了 FF 單元給 thread 傳遞的參數。R1 等寄存器會包含 URB handle, thread 需要讀取/寫入 頂點/patch 等數據可以指定 handle 從 URB 中加載 /寫入。
? Thread Payload Input URB Data: thread 從 URB 加載數據和常量。
對于每個 URB entry,FF 單元將提供一系列 handle、讀取偏移量和讀取長度。線程調度子系統將讀取 URB 的適當 256 bit 位置,并將結果寫入順序 GRF 寄存器(寫入 GRF)
thread 預加載(payload)數據的布局
Slice /Sub Slice
如圖 一個 slice 里包含多個 subslice,一個 L3 cache, 一塊共享的顯存,一個硬件的內存屏障還有固定函數單元(fixed function units)
?
Pipeline Stage
A abstracted element of the 3D pipeline, providing functions performed by a combination of the corresponding hardware FF unit and the threads spawned by that FF unit.
一個 3D pipeline 有多個階段:
每個階段通過固定函數單元完成相應的功能,藍色的框固定函數單元是硬件固定不可更改的功能;
黃色代表可非固定功能,功能由軟件編程后經過編譯器編譯在可編程單元(EU)中執行;
fixed function 現在更像是一個概念不一定需要獨立的硬件去實現他的功能。
一個 subslice 內包含有多個 EU,一個 Dispatcher,一個 sampler,一個 Data Port。
?
sampler
3D 采樣引擎提供了高級采樣和過濾表面的能力。采樣引擎功能負責向 EU 提供過濾后的紋理值,以響應采樣引擎收到的 message 消息。采樣引擎使用 SAMPLER_STATE 來控制濾波模式 、地址控制模式等采樣引擎的其他功能。每個消息都傳遞一個指向 sampler state 的指針。此外,采樣引擎使用 SURFACE_STATE 來定義被采樣的 suface 屬性。這包括 surface 的位置、大小和格式 以及其他屬性。?
采樣引擎的子功能:
1. sampler 主要流程:
surface:A rendering operand or destination, including textures, buffers, and render targets
Surface state :State associated with a render surface including
進行采樣處理需要 surface state objects (RENDER_SURFACE_STATE), surface data,以及 sampler state。采樣器通過 surface state objects 來獲取 surface 在 system 中地址 以及 surface 的格式。采樣器獲取到了支持的格式的 surface 就會自動解壓。采樣器支持的濾波模式有(point, bilinear, trilinear, anisotropic, cube etc.) ,具體處理時根據 SAMPLER_STATE state object 內容來確定使用哪個模式參數。
2. 主要指令
GPU 維護了一個 bind table 表,每種類型的 shader 都有一個綁定表。Send 發送命令到 sampler 時 data 里包含了 bind table 的基地址和表里 entry。通過綁定表找到要使用的 surface state。
根據 手冊每種 shader 都有自己的 BTP,也就是每個 shader 都有一個 bind 表。BTP 是通過 3DSTATE_BINDING_TABLE_POINTERS_XXX 命令指定。
BTPB 通過 3DSTATE_BINDING_TABLE_POOL_ALLOC 命令設置。
Surface State Base address 通過 STATE_BASE_ADDRESS 命令指定。STATE_BASE_ADDRESS 作為一個通用的基地址設置寄存器可以設置多個基地址:
General State Base Address;
Surface State Base Address;
Dynamic State Base Address;
Indirect Object Base Address ;
Instruction Base Address ;
Bindless Surface State Base Address;
Bindless Sampler State Base Address;
sampler state 則是通過 message 消息中包含的 Sampler State Pointer 獲取到偏移地址,和 DynamicStateBaseAddress 基地址組合找到位于 system 種的 sampler state。Sampler State Pointer 是通過3DSTATE_SAMPLER_STATE_POINTERS_xx 設置的。
3. Mesa 中的實現
mesa 中設置 surface base addr 是調用的 update_surface_base_address 函數。
在 blorp_emit_surface_states 函數中對調用 blorp_alloc_binding_table 分配新的 bind table 會執行 update_surface_base_address 基地址。
mesa 中的 blorp_emit_surface_states 函數會執行 3DSTATE_BINDING_TABLE_POINTERS_xxx 將每種 shader 設置 BTP,該 BTP 維護在 struct iris_binding_table 中
?
DataPort
采樣器只能讀取, DataPort 具有讀寫功能,提供了所有的內存的訪問通道。
context
?
Execlists
Execution-List 是硬件提供的軟件接口。SW 通過將要執行的命令填寫到 context 對應的 ring buffer 中,然后講 context 提交給引擎的 Execlist Submit ?Port (ELSP), dg1 上有兩個 ELSP。Execution-List 使用 需要引擎的 GFX_MODE 寄存器設置相應的 enable。
?
Context state
每個 context 根據其工作負載要求對引擎狀態進行編程。硬件執行需要修改的狀態稱為 context state。每個 congtext 都有自己的 context state。Context state 在執行當前 context ring buffer 的命令時被修改。指定在引擎上運行的所有 context 都具有相同的上下文格式。context 通過 Logical Context Address 指向了存儲 context state 的地址。
?
Logical Context Address
logical context address 是保存硬件信息(context state)的一個全局虛擬地址(GPU 的虛擬地址),GPU context 切換通過 logical context ?address 來完成 context state 的保存和加載。當調度器通過 Execution-List 提交一個工作負載列表時,命令流媒體硬件一次執行一次 context 切換。引擎通過 logical context ?address(LRCA)來加載 logical context。
?
Context state 包含內容
the following sections: ? Per-Process HW Status Page (4K) ? Ring Context (Ring Buffer Control Registers, Page Directory Pointers, etc.)
? Engine Context ( PipelineState, Non-pipelineState, Statistics, MMIO)
context 的生命周期
Mesa 3D 代碼實現分析
圖 渲染pipeline 各階段
黃色的階段代表沒有固定的硬件來完成這個功能,而是由 CS 中的固定函數單元發起一個 EU thread 來完成功能。也就是黃色的階段都是可編程的。藍色的是由固定硬件完成,不可編程。
?
Vertex Fetch (VF) stage
頂點獲取階段。當 3D Primitive 命令下發后,VF 負責從內存中讀取頂點數據,重新格式化它,并將結果寫入頂點 URB 條目中。VF 單元會生成 instanceID,VertexID 和 PrimitiveID 這些數據的組合將會寫入到 VUE 中。
VF 的數據處理
3D_Primitive 之前要先下發 3D pipeline state 相關的命令,這樣 3D_Primitive 解析到后 CS 會讓 VF 硬件開始工作去獲取數據然后處理數據,從用戶的 batchbuffer 3D_Primitive 命令位置反方向解析之前的 3D pipeline 相關命令。STATE_BASE_ADDRESS 命令用于設置基地址,后面下發命令使用 bind table/surface state 地址都與這個基地址有關。在 mesa 中 src/gallium/drivers/iris/iris_bufmgr.h 中有介紹相關的內存分配情況:
/** * Memory zones. When allocating a buffer, you can request that it is * placed into a specific region of the virtual address space (PPGTT). * * Most buffers can go anywhere (IRIS_MEMZONE_OTHER). Some buffers are * accessed via an offset from a base address. STATE_BASE_ADDRESS has * a maximum 4GB size for each region, so we need to restrict those * buffers to be within 4GB of the base. Each memory zone corresponds * to a particular base address. * * We lay out the virtual address space as follows: * * - [0, 4K): Nothing (empty page for null address) * - [4K, 4G): Shaders (Instruction Base Address) * - [4G, 8G): Surfaces & Binders (Surface State Base Address, Bindless ...) * - [8G, 12G): Dynamic (Dynamic State Base Address) * - [12G, *): Other (everything else in the full 48-bit VMA) */ 相應的mesa 中將ppgtt使用的虛擬地址也分成了幾個zone enum iris_memory_zone { IRIS_MEMZONE_SHADER, IRIS_MEMZONE_BINDER, IRIS_MEMZONE_SCRATCH, IRIS_MEMZONE_SURFACE, IRIS_MEMZONE_DYNAMIC, IRIS_MEMZONE_OTHER, IRIS_MEMZONE_BORDER_COLOR_POOL, };
?
3D pipeline 狀態設置命令
VF 階段接收來自 CS 單元的 3DPRIMITIVE 命令信息,執行該命令,將生成的頂點數據存儲在 URB 中,并將相應的頂點信息包向下傳遞給其他的管道 stage。在 3D primitives 命令發出之前,要如下命令設置 Pipeline 各種狀態: ? 3DSTATE_PIPELINED_POINTERS (gen45 后這個命令沒了) ? 3DSTATE_BINDING_TABLE_POINTERS ? 3DSTATE_VERTEX_BUFFERS ? 3DSTATE_VERTEX_ELEMENTS ? 3DSTATE_INDEX_BUFFERS ? 3DSTATE_VF_STATISTICS ? 3DSTATE_DRAWING_RECTANGLE ? 3DSTATE_CONSTANT_COLOR ? 3DSTATE_DEPTH_BUFFER ? 3DSTATE_POLY_STIPPLE_OFFSET ? 3DSTATE_POLY_STIPPLE_PATTERN ? 3DSTATE_LINE_STIPPLE
? 3DSTATE_GLOBAL_DEPTH_OFFSET
和 VF 頂點輸入有關的命令有:? ? 3DSTATE_VERTEX_BUFFERS(頂點 buffer VBO)? ? 3DSTATE_VERTEX_ELEMENTS(頂點屬性配置)? ? 3DSTATE_INDEX_BUFFERS(索引 buffer EBO)
? 3DPRIMITIVE(圖元裝配)?
? 3DSTATE_VF_STATISTICS(頂點信息統計)
vertex buffer
3DSTATE_VERTEX_BUFFERS 和 3DSTATE_VERTEX_STATE 命令 用來定義存放頂點的 buffer。3D Primitive 中使用的頂點 buffer 大部分來自頂點 buffer。在 OpenGL 中對應的 VBO(Vertex Buffer Object)。
float vertices[] = { 0.5f, 0.5f, 0.0f, // 右上角 0.5f, -0.5f, 0.0f, // 右下角 -0.5f, -0.5f, 0.0f, // 左下角 -0.5f, 0.5f, 0.0f // 左上角 }; // create VBOs glGenBuffers(1, &vboId); // for vertex buffer glBindBuffer(GL_ARRAY_BUFFER, vboId); // copy vertex attribs data to VBO glBufferSubData(GL_ARRAY_BUFFER, 0, vSize, vertices);//將頂點數據寫入到VBO中VB(vertex buffer)是一個一維結構的數組,其中結構的大小由 VB 的緩沖間距定義。3DPrimitive 對 vertex buffer 的訪問有順序和隨機兩種方式。當 OpenGL 沒有使用頂點 index buffer object(element buffer object)的時候會順序讀.使用 index buffer object 會根據 buffer 指定 index 在 buffer 中隨機讀取。?
?
3DSTATE_VERTEX_STATE 是用戶表示數據緩沖區和實例化數據緩沖區的一個結構,是一個 4 個 Dword 大小的 buffer。一個 3DSTATE_VERTEX_BUFFERS 可以指定多個 3DSTATE_VERTEX_STATE(至少包含一個),最多綁定 33 個 3DSTATE_VERTEX_STATE。
mesa 中 src/gallium/drivers/iris/iris_state.c 文件用來給 batchbuffer 填充 3D pipeline state 相關的命令。iris_upload_dirty_render_state 函數將 3DSTATE_VERTEX_BUFFERS 寫入到 batch buffer 中。
const unsigned vb_dwords = GENX(VERTEX_BUFFER_STATE_length); uint32_t *map = iris_get_command_space(batch, 4 * (1 + vb_dwords * count)); _iris_pack_command(batch, GENX(3DSTATE_VERTEX_BUFFERS), map, vb) {//寫入命令 vb.DWordLength = (vb_dwords * count + 1) - 2; } map += 1; bound = dynamic_bound; while (bound) { const int i = u_bit_scan64(&bound); memcpy(map, genx->vertex_buffers[i].state, //3DSTATE_VERTEX_STATE 數據拷貝到batchbuffer中 sizeof(uint32_t) * vb_dwords); map += vb_dwords; } }在 opengl 調用 genbuffer 創建 vbo 再寫入數據后 mesa 后端 pipe 會調用 pipe->set_vertex_buffers, 調用 iris_set_vertex_buffers 函數將 buffer 的地址 大小等數據 xieru 到 vertex buffer state 結構中。?
?
3DSTATE_VERTEX_BUFFERS 命令寫入時候將存儲的 vertex buffer state 數據一起寫入到 batch buffer 中。
static void iris_set_vertex_buffers(struct pipe_context *ctx, unsigned start_slot, unsigned count, unsigned unbind_num_trailing_slots, bool take_ownership, const struct pipe_vertex_buffer *buffers) { .............省略 iris_pack_state(GENX(VERTEX_BUFFER_STATE), state->state, vb) { vb.VertexBufferIndex = start_slot + i; vb.AddressModifyEnable = true; vb.BufferPitch = buffer->stride; if (res) { vb.BufferSize = res->base.b.width0 - (int) buffer->buffer_offset; vb.BufferStartingAddress = ro_bo(NULL, res->bo->address + (int) buffer->buffer_offset); vb.MOCS = iris_mocs(res->bo, &screen->isl_dev, ISL_SURF_USAGE_VERTEX_BUFFER_BIT); #if GFX_VER >= 12 vb.L3BypassDisable = true; #endif } else { vb.NullVertexBuffer = true; vb.MOCS = iris_mocs(NULL, &screen->isl_dev, ISL_SURF_USAGE_VERTEX_BUFFER_BIT); } } } .........省略 }iris_pack_state 宏定義是根據 mesa 中的 xml 生成了,如果不編譯源碼是沒有這個文件的,生成目錄 build/src/intel/genxml/gen12_pack.h。
?
index buffer
3DSTATE_INDEX_BUFFERS 命令用來配置 index buffer 相關 state(address/size 等等)index buffer 在 OpenGL 中也叫做 Element buffer 也就是 EBO(Element buffer object)。
float vertices[] = { 0.5f, 0.5f, 0.0f, // 右上角 0.5f, -0.5f, 0.0f, // 右下角 -0.5f, -0.5f, 0.0f, // 左下角 -0.5f, 0.5f, 0.0f // 左上角 }; unsigned int indices[] = { 0, 1, 3, // 第一個三角形 1, 2, 3 // 第二個三角形 }; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);EBO 是為了解決一個頂點多次重復調用的問題,可以減少內存的空間浪費,提高執行效率,當重復使用重復頂點時,通過頂點的位置索引來調用頂點。而不是重復調用頂點的數據。
?
EBO 中存儲的內容就是頂點位置的索引 indices,EBO 類似于 VBO,也是在顯存中分配的一個 bufer,只不過 EBO 存放的是頂點的索引。GPU 中 index buffer 結構布局按照手冊中的 3DSTATE_INDEX_BUFFER_BODY 格式 mesa 中的 iris_upload_render_state 函數將 3DSTATE_INDEX_BUFFERS xieru 到 batch buffer 中。
uint32_t ib_packet[GENX(3DSTATE_INDEX_BUFFER_length)]; iris_pack_command(GENX(3DSTATE_INDEX_BUFFER), ib_packet, ib) { ib.IndexFormat = draw->index_size >> 1; ib.MOCS = iris_mocs(bo, &batch->screen->isl_dev, ISL_SURF_USAGE_INDEX_BUFFER_BIT); ib.BufferSize = bo->size - offset; ib.BufferStartingAddress = ro_bo(NULL, bo->address + offset); #if GFX_VER >= 12 ib.L3BypassDisable = true; #endif }
?
index format 對應的是 opengl 中的索引值的類型:GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT。
3DSTATE_INDEX_BUFFERS 和 3DSTATE_VERTEX_BUFFERS 都有 bo->address,這個 bo address 并不是 CPU 側映射的虛擬地址,而是通過上面說的給 ppgtt 使用的虛擬地址。從 mesa 驅動劃分的不同 zone 分配出來的。
?
enum iris_memory_zone { IRIS_MEMZONE_SHADER, IRIS_MEMZONE_BINDER, IRIS_MEMZONE_SCRATCH, IRIS_MEMZONE_SURFACE, IRIS_MEMZONE_DYNAMIC, IRIS_MEMZONE_OTHER, IRIS_MEMZONE_BORDER_COLOR_POOL, };mesa 用戶態驅動 bo 的分配必須指定自己使用的 mem zone。
iris_bo_alloc(struct iris_bufmgr *bufmgr, const char *name, uint64_t size, uint32_t alignment, enum iris_memory_zone memzone, unsigned flags)從指定的 zone 分配出來虛擬地址后, 這個虛擬地址寫入到 batch 中,根據之前設置的 base 基地址。GPU 驅動會強制使用這個地址做 PPGTT(Per-Process Graphics Translation Table) 的虛擬地址映射。
?
頂點屬性
有了 VBO/EBO 后頂點數據和頂點使用順序已經有了,VBO 存儲頂點數據有不同的格式,GPU 硬件按照什么格式解析 VBO 數據需要告訴 GPU。在 opengl 中 glVertexAttribPointer 和 glvertexattribformat 用來設置 buffer 中頂點數據的解析方式。
// 0. 復制頂點數組到緩沖中供OpenGL使用 glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 1. 設置頂點屬性指針 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); 來自https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ glVertexAttribPointer`函數的參數非常多,所以我會逐一介紹它們: - 第一個參數指定我們要配置的頂點屬性。在頂點著色器中使用`layout(location = 0)`定義了`position`頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為`0`。因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入`0`。 - 第二個參數指定頂點屬性的大小。頂點屬性是一個`vec3`,它由3個值組成,所以大小是3。 - 第三個參數指定數據的類型,這里是`GL_FLOAT`(GLSL中`vec*`都是由浮點數值組成的)。 - 下個參數定義我們是否希望數據被標準化(Normalize)。如果我們設置為`GL_TRUE`,所有數據都會被映射到0(對于有符號型signed數據是-1)到1之間。我們把它設置為`GL_FALSE`。 - 第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由于下個組位置數據在3個`float`之后,我們把步長設置為`3 * sizeof(float)`。要注意的是由于我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用)。一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數的意思簡單說就是從這個屬性第二次出現的地方到整個數組0位置之間有多少字節)。 - 最后一個參數的類型是`void*`,所以需要我們進行這個奇怪的強制類型轉換。它表示位置數據在緩沖中起始位置的偏移量(Offset)。由于位置數據在數組的開頭,所以這里是0stride offset 和 stride 配置可以參考:?
https://stackoverflow.com/questions/16380005/opengl-3-4-glvertexattribpointer-stride-and-offset-miscalculation
?
在 GPU 中使用 VERTEX_ELEMENT_STATE 來設置頂點的屬性。3DSTATE_VERTEX_ELEMENTS 設置有多少個 VERTEX_ELEMENT_STATE. mesa 代碼中通過 struct pipe_vertex_element 結構維護頂點屬性
?
struct pipe_vertex_element { /** Offset of this attribute, in bytes, from the start of the vertex */ uint16_t src_offset; /** Which vertex_buffer (as given to pipe->set_vertex_buffer()) does * this attribute live in? */ uint8_t vertex_buffer_index:7; /** * Whether this element refers to a dual-slot vertex shader input. * The purpose of this field is to do dual-slot lowering when the CSO is * created instead of during every state change. * * It's lowered by util_lower_uint64_vertex_elements. */ bool dual_slot:1; /** * This has only 8 bits because all vertex formats should be <= 255. */ uint8_t src_format; /* low 8 bits of enum pipe_format. */ /** Instance data rate divisor. 0 means this is per-vertex data, * n means per-instance data used for n consecutive instances (n > 0). */ unsigned instance_divisor; };pipe->create_vertex_elements_state 來設置頂點屬性,intel iris_create_vertex_elements 寫 VERTEX_ELEMENT_STATE
iris_pack_state(GENX(VERTEX_ELEMENT_STATE), ve_pack_dest, ve) { ve.EdgeFlagEnable = false; ve.VertexBufferIndex = state[i].vertex_buffer_index; ve.Valid = true; ve.SourceElementOffset = state[i].src_offset; ve.SourceElementFormat = fmt.fmt; ve.Component0Control = comp[0]; ve.Component1Control = comp[1]; ve.Component2Control = comp[2]; ve.Component3Control = comp[3]; }
?
頂點數據處理
3DPrimitive 命令下發后,根據 batch buffer 配置的頂點 buffer 地址,格式,index 等信息 GPU 處理頂點數據,將頂點數據處理 生成 vertexid ,vertexindex 等后寫入到 URB 中,生成獨一的 URB handle, VS 通過 URB handle 配合 shader 處理數據。3DPRIMITIVE 命令指定數據格式:
iris_emit_cmd(batch, GENX(3DPRIMITIVE), prim) { prim.VertexAccessType = draw->index_size > 0 ? RANDOM : SEQUENTIAL;/是否存在index buffer 存在就是隨機讀取 prim.PredicateEnable = use_predicate; if (indirect) { prim.IndirectParameterEnable = true; } else { prim.StartInstanceLocation = draw->start_instance; prim.InstanceCount = draw->instance_count; prim.VertexCountPerInstance = sc->count; prim.StartVertexLocation = sc->start;//對應opengl 中glDrawArrays 的start if (draw->index_size) { prim.BaseVertexLocation += sc->index_bias;//頂點的固定偏移,類似于glDraw[Range]Elements{,BaseVertex}" api 指定BaseVertex } } }
?
頂點數據的讀取寫入 URE 大體流程
3D Primitives 處理頂點數據偽代碼(來自 gen4 手冊)
vertexloop 會計算當前的 VERTEXID,順序讀寫生成 VERTEXID 偽代碼:
VertexIndex = StartVertexLocation + VertexNumber VertexID = VertexNumber
?
隨機讀寫偽代碼:
?
IBIndex?=?StartVertexLocation?+?VertexNumber?? VertexID = IB[IBIndex] if (VertexID == ‘all ones’) CutFlag = 1 else VertexIndex = VertexID + BaseVertexLocation CutFlag = 0 endif相比順序讀寫,隨機讀寫是從 index buffer 查找頂點的位置,所以多了個 index buffer 的過程。
?
PrimitiveID 是每個 PrimitiveI type 每個的 id,下圖比較形象
Vertexid, PrimitiveID,頂點數據的格式轉換,最后生成 vue handle 給 VS 階段使用。
?
Vertex Shader (VS) Stage
VF 處理完成頂點數據后傳輸到 pipeline 下個 stage VS。VS 通過 URB handle 讀取 shader 需要的數據,發起 EU 上的線程執行 shader。發起 thread 是通過 Thread Dispatcher 完成. shader 編譯器會根據 shader 的內容生成使用的常量 buffer,使用的 surface,采樣器等信息。這些 state 通過 3DSTATE_XX 命令下發下去。VS 使用 3DSTATE_VS。
這些信息通過 3DSTATE_VS 命令下發給 GPU,在 mesa /src/gallium/drivers/iris/iris_state.c 下發所有和 state 相關的信息。
#define?INIT_THREAD_DISPATCH_FIELDS(pkt,?prefix,?stage)??????????????????? pkt.KernelStartPointer = KSP(shader); pkt.BindingTableEntryCount = shader->bt.size_bytes / 4; pkt.FloatingPointMode = prog_data->use_alt_mode; pkt.DispatchGRFStartRegisterForURBData = prog_data->dispatch_grf_start_reg; pkt.prefix##URBEntryReadLength = vue_prog_data->urb_read_length; pkt.prefix##URBEntryReadOffset = 0; pkt.StatisticsEnable = true; pkt.Enable = true; if (prog_data->total_scratch) { struct iris_bo *bo = iris_get_scratch_space(ice, prog_data->total_scratch, stage); uint32_t scratch_addr = bo->gtt_offset; pkt.PerThreadScratchSpace = ffs(prog_data->total_scratch) - 11; pkt.ScratchSpaceBasePointer = rw_bo(NULL, scratch_addr, IRIS_DOMAIN_NONE); } /** * Encode most of 3DSTATE_VS based on the compiled shader. */ static void iris_store_vs_state(struct iris_context *ice, const struct gen_device_info *devinfo, struct iris_compiled_shader *shader) { struct brw_stage_prog_data *prog_data = shader->prog_data; struct brw_vue_prog_data *vue_prog_data = (void *) prog_data; iris_pack_command(GENX(3DSTATE_VS), shader->derived_data, vs) { INIT_THREAD_DISPATCH_FIELDS(vs, Vertex, MESA_SHADER_VERTEX); vs.MaximumNumberofThreads = devinfo->max_vs_threads - 1; vs.SIMD8DispatchEnable = true; vs.UserClipDistanceCullTestEnableBitmask = vue_prog_data->cull_distance_mask; } }
?
?
采樣器 state/table 的創建
在 GPU 中每個 shader stage 都有自己的 sampler state 表,然后通過 3DSTATE_SAMPLER_STATE_POINTERS_XX 設置到硬件。
Mesa gallium 分為前端和后端。OpenGL 后端實現都是基于 pipe 結構調用硬件驅動。ctx->pipe->create_sampler_states//創建一個基于硬件支持的 sample state 結構 ctx->pipe->bind_sampler_states //將創建的 sample state 結構和 VS/FS 等階段綁定
OpenGL 中紋理采樣器的使用
void glGenSamplers (GLsizei count, GLuint *samplers); void glSamplerParameteri (GLuint sampler, GLenum pname, GLint param); void glSamplerParameterf (GLuint sampler, GLenum pname, GLfloat param); void glBindSampler(GLuint unit, GLuint sampler);
?
mesa 中維護的 sampler 結構
gl_sampler_object 轉換成pipe_sampler_state,然后update_shader_samplers—>cso_set_samplers-> cso_single_sampler->cso_single_sampler->pipe->create_sampler_states ->iris_create_sampler_state最后調用intel 驅動iris_create_sampler_state 將數據寫入到硬件支持的buffer里。 mesa中通過cso_single_sampler_done函數將sampler state和3d pipeline stage 綁定 void cso_single_sampler_done(struct cso_context *ctx, enum pipe_shader_type shader_stage) { struct sampler_info *info = &ctx->samplers[shader_stage]; if (ctx->max_sampler_seen == -1) return; ctx->pipe->bind_sampler_states(ctx->pipe, shader_stage, 0, ctx->max_sampler_seen + 1, info->samplers); ctx->max_sampler_seen = -1; }最后 iris_upload_sampler_states 函數中會分配一個 sampler_table 的 bo,然后將 sample state 數據拷貝到 bo 中,然后通過 3DSTATE_SAMPLER_STATE_POINTERS_VS 命令下發到 GPU。
?
?
Sampler State ?TABLE
sampler state 則是通過 message 消息中包含的 Sampler State Pointer 獲取到偏移地址,和 DynamicStateBaseAddress 基地址組合找到位于 system 種的 sampler state。Sampler State Pointer 是通過 3DSTATE_SAMPLER_STATE_POINTERS_xx 設置的。
for (int stage = 0; stage <= MESA_SHADER_FRAGMENT; stage++) { if (!(stage_dirty & (IRIS_STAGE_DIRTY_SAMPLER_STATES_VS << stage)) || !ice->shaders.prog[stage]) continue; iris_upload_sampler_states(ice, stage); //更新sampler state 到sampler_table中 struct iris_shader_state *shs = &ice->state.shaders[stage]; struct pipe_resource *res = shs->sampler_table.res; if (res) iris_use_pinned_bo(batch, iris_resource_bo(res), false, IRIS_DOMAIN_NONE); iris_emit_cmd(batch, GENX(3DSTATE_SAMPLER_STATE_POINTERS_VS), ptr) { ptr._3DCommandSubOpcode = 43 + stage; ptr.PointertoVSSamplerState = shs->sampler_table.offset; } }
?
Mesa ?gallium 分為前端和后端。Opengl 后端實現都是基于 pipe 結構調用硬件用戶態驅動。
ctx->pipe->create_sampler_states //創建一個基于硬件支持的 sample state 結構
ctx->pipe->bind_sampler_states // 將 sample state 結構和 VS FS 等 state 階段綁定
glGenSamplers 會創建一個采樣器 glBindSampler 綁定一個采樣器 glSamplerParameter 設置采樣器參數等等 api 會在 mesa 創建由khronos.org OpenGL 定義的ARB_sampler_objects 結構體并且設置相關的參數。在 draw 時候調用 update_shader_samplers, 在 update 中 st_convert_sampler(函數將
gl_sampler_object 轉換成 pipe_sampler_state,然后 update_shader_samplers—>cso_set_samplers-> cso_single_sampler->cso_single_sampler->pipe->create_sampler_states ->iris_create_sampler_state, 最后調用驅動 iris_create_sampler_state 將數據寫入到硬件支持的 SAMPLER_STATE 格式 buffer 里。
iris_create_sampler_state(struct pipe_context *ctx, const struct pipe_sampler_state *state) { struct iris_sampler_state *cso = CALLOC_STRUCT(iris_sampler_state); ...... float min_lod = state->min_lod; unsigned mag_img_filter = state->mag_img_filter; // XXX: explain this code ported from ilo...I don't get it at all... if (state->min_mip_filter == PIPE_TEX_MIPFILTER_NONE && state->min_lod > 0.0f) { min_lod = 0.0f; mag_img_filter = state->min_img_filter; } iris_pack_state(GENX(SAMPLER_STATE), cso->sampler_state, samp) { samp.TCXAddressControlMode = wrap_s; samp.TCYAddressControlMode = wrap_t; samp.TCZAddressControlMode = wrap_r; samp.CubeSurfaceControlMode = state->seamless_cube_map; samp.NonnormalizedCoordinateEnable = !state->normalized_coords; samp.MinModeFilter = state->min_img_filter; samp.MagModeFilter = mag_img_filter; samp.MipModeFilter = translate_mip_filter(state->min_mip_filter); samp.MaximumAnisotropy = RATIO21; if (state->max_anisotropy >= 2) { if (state->min_img_filter == PIPE_TEX_FILTER_LINEAR) { samp.MinModeFilter = MAPFILTER_ANISOTROPIC; samp.AnisotropicAlgorithm = EWAApproximation; } if (state->mag_img_filter == PIPE_TEX_FILTER_LINEAR) samp.MagModeFilter = MAPFILTER_ANISOTROPIC; samp.MaximumAnisotropy = MIN2((state->max_anisotropy - 2) / 2, RATIO161); } }在 cso_set_samplers 函數中分配的 sampler 結構指針賦值給了 ctx 的 samplers 中
cso_single_sampler(struct cso_context *ctx, enum pipe_shader_type shader_stage, unsigned idx, const struct pipe_sampler_state *templ) { if (templ) { unsigned key_size = sizeof(struct pipe_sampler_state); unsigned hash_key = cso_construct_key((void*)templ, key_size); struct cso_sampler *cso; struct cso_hash_iter iter = cso_find_state_template(ctx->cache, hash_key, CSO_SAMPLER, (void *) templ, key_size); if (cso_hash_iter_is_null(iter)) { cso = MALLOC(sizeof(struct cso_sampler)); if (!cso) return; memcpy(&cso->state, templ, sizeof(*templ)); cso->data = ctx->pipe->create_sampler_state(ctx->pipe, &cso->state); cso->delete_state = (cso_state_callback) ctx->pipe->delete_sampler_state; cso->context = ctx->pipe; cso->hash_key = hash_key; iter = cso_insert_state(ctx->cache, hash_key, CSO_SAMPLER, cso); if (cso_hash_iter_is_null(iter)) { FREE(cso); return; } } else { cso = cso_hash_iter_data(iter); } ctx->samplers[shader_stage].cso_samplers[idx] = cso; ctx->samplers[shader_stage].samplers[idx] = cso->data; ctx->max_sampler_seen = MAX2(ctx->max_sampler_seen, (int)idx); } }
?
將 create sample 創建的 state 發動給驅動, 和 stage 綁定
?
/** * Send staged sampler state to the driver. */ void cso_single_sampler_done(struct cso_context *ctx, enum pipe_shader_type shader_stage) { struct sampler_info *info = &ctx->samplers[shader_stage]; if (ctx->max_sampler_seen == -1) return; ctx->pipe->bind_sampler_states(ctx->pipe, shader_stage, 0, ctx->max_sampler_seen + 1, info->samplers); ctx->max_sampler_seen = -1; }
/** * The pipe->bind_sampler_states() driver hook. */ static void iris_bind_sampler_states(struct pipe_context *ctx, enum pipe_shader_type p_stage, unsigned start, unsigned count, void **states) { struct iris_context *ice = (struct iris_context *) ctx; gl_shader_stage stage = stage_from_pipe(p_stage); struct iris_shader_state *shs = &ice->state.shaders[stage]; assert(start + count <= IRIS_MAX_TEXTURE_SAMPLERS); bool dirty = false; for (int i = 0; i < count; i++) { if (shs->samplers[start + i] != states[i]) { shs->samplers[start + i] = states[i]; dirty = true; } } if (dirty) ice->state.stage_dirty |= IRIS_STAGE_DIRTY_SAMPLER_STATES_VS << stage; }
?
最后 iris_upload_sampler_states 函數中會分配一個 sampler_table 的 bo,然后將 sample state 數據拷貝到 bo 中,然后通過 3DSTATE_SAMPLER_STATE_POINTERS_VS 命令下發到 GPU。
?
STATE BIND TABLE
opengl 使用的紋理,image 等在驅動中都是以 surface 來表示。suface state 保存了地址 屬性等信息。state binder table 里 entry 存放了 surface state。每個 shader stage 都有自己的 state binder table,使用 3DSTATE_BINDING_TABLE_POINTERS_XX 命令配置到 GPU。
for (int stage = 0; stage <= MESA_SHADER_FRAGMENT; stage++) { /* Gen9 requires 3DSTATE_BINDING_TABLE_POINTERS_XS to be re-emitted * in order to commit constants. TODO: Investigate "Disable Gather * at Set Shader" to go back to legacy mode... */ if (stage_dirty & ((IRIS_STAGE_DIRTY_BINDINGS_VS | (GEN_GEN == 9 ? IRIS_STAGE_DIRTY_CONSTANTS_VS : 0)) << stage)) { iris_emit_cmd(batch, GENX(3DSTATE_BINDING_TABLE_POINTERS_VS), ptr) { ptr._3DCommandSubOpcode = 38 + stage; ptr.PointertoVSBindingTable = binder->bt_offset[stage]; } } }binder->bt_offset[stage] 是各個 state 的狀態表。bt_offset 是保存的地址。在 mesa 中將 ppgtt 使用的虛擬地址都叫做 offset。mesa 通過 heap 分配出這個虛擬地址,然后通過 exec2 下發給驅動,驅動不修改該虛擬地址并強制建立映射關系。
?
State Bind Table 初始化
OpenGL 創建 context 會調用 iris_create_context,初始化 context 時調用 iris_init_binder 分配一塊 bo,用來做 state bind table 的 buffer。
?
binder_realloc(struct iris_context *ice) { struct iris_screen *screen = (void *) ice->ctx.screen; struct iris_bufmgr *bufmgr = screen->bufmgr; struct iris_binder *binder = &ice->state.binder; uint64_t next_address = IRIS_MEMZONE_BINDER_START; if (binder->bo) { /* Place the new binder just after the old binder, unless we've hit the * end of the memory zone...then wrap around to the start again. */ next_address = binder->bo->gtt_offset + IRIS_BINDER_SIZE; if (next_address >= IRIS_MEMZONE_SURFACE_START) next_address = IRIS_MEMZONE_BINDER_START; iris_bo_unreference(binder->bo); } binder->bo = iris_bo_alloc(bufmgr, "binder", IRIS_BINDER_SIZE, IRIS_MEMZONE_BINDER, 0); binder->bo->gtt_offset = next_address; binder->map = iris_bo_map(NULL, binder->bo, MAP_WRITE); binder->insert_point = INIT_INSERT_POINT; }
?
填充 state bind table
state bind table 中放的是 OpenGL 使用的各種 surface 的 state,mesa 中 iris_setup_binding_table 是 GPU 通過編譯 shader 判斷需要有多少 surface 在 shader 中使用, 然后計算出每種 surface state 的大小。在 mesa ?iris_populate_binding_table 函數中往前面分配的 bind buffer 中 填寫數據 。
總體 kernel Pointer/ sampler state/bind table 使用如下圖:
審核編輯:湯梓紅
評論
查看更多