學廢了系列 - WebGL與Node.js中的Buffer

WebGL 和 Node.js 中都有 Buffer 的使用,簡單對比記錄一下兩個徹底不相干的領域中 Buffer 異同,增強記憶。面試

Buffer 是用來存儲二進制數據的「緩衝區」,其自己的定義和用途在任何技術領域都是一致的,跟 WebGL 和 Node.js 沒有直接關係,二者惟一的共同點就是都使用 JavaScript。數組

在 ES6 將TypedArray二進制類型數組)正式加入 ECMA 標準以前,JavaScript 語言自己並無標準的處理二進制數據的能力,Buffer 就是爲了彌補這一缺陷。緩存

TypedArray 成爲 ECMA 標準以前就已經在 WebGL 領域普遍使用了。網絡

Node.js 加入 Buffer 的做用主要是爲了處理 stream,好比網絡流、文件流等等。Buffer 佔用預申請的一整片內存,stream 被消費的速度若是低於接收速度,就會被暫存在緩衝區內,而後被消費者從緩存區依序取出消費。app

Node.js 中的 Buffer 是 Uint8Array 的子類,Uint8Array 是ECMA 標準中 TypedArray 中的一種數據類型。性能

console.log(Buffer.__proto__) 
// 打印 [Function: Uint8Array]

其實 Node.js 中的 Buffer 與 ECMA 標準的 TypedArray 並無直接關係,Node.js 很早期的版本(v0.10.0)版本就支持了 Buffer。Uint8Array,或者說 ECMA 標準中全部的 TypedArray 都是 JavaScript 引擎提供的一種 API,早期未被加入 ECMA 標準的時候就已經有很多引擎實現了這些 API,而最先使用二進制類型數組的場景就是 WebGL。指針

話說回來,ECMA 標準作的不就是「集百家之長」(修辭手法-反諷)的事嗎哈哈😂code

而後說到 WebGL 中的 Buffer。對象

WebGL 有兩種 Buffer 類型:blog

  • ARRAY_BUFFER:頂點屬性數據的 Buffer,用來傳遞任何跟頂點相關的數據,好比座標、顏色等等。這些數據通常是浮點數,最經常使用的類型是 Float32Array
  • ELEMENT_ARRAY_BUFFER:元素索引數據的 Buffer,用來傳遞讀取 ARRAY_BUFFER 元素的順序。每一個元素必須是整數,使用 Uint8Array,這一點跟 Node.js 中的 Buffer 一致。此 buffer 是可選項,若是不使用的話 ,ARRAY_BUFFER 的元素會被按照 index 依序讀取。

雖然 WebGL 中沒有 stream 的概念(嚴格來講是從開發者的認知層面沒有 stream,底層 OpenGL 處理 buffer 數據的流程中是有 stream 的),但 Buffer 的做用跟 Node.js 是一致的,都是將數據暫存在一整片預申請的內存中,供後續進程邏輯消費,區別是消費者不一樣。

在WebGL渲染管線中,但從CPU到GPU完整的數據傳輸鏈路中,有如下幾種buffer:

  • VBO,Vertex Buffer Object,頂點緩衝對象儲存頂點屬性數據,消費者是 shader,嚴格的說是 vertex shader;
  • FBO,Fragment Buffer Object,幀緩衝對象能夠簡單理解爲一個指針集合體,附着 RBO、顏色、紋理等用於渲染的全部信息;
  • RBO,Rendering Buffer Object,渲染緩衝對象儲存 depth(深度)、stencil(模板)值。

FBO 與 RBO、紋理的關係以下圖:

另一點須要瞭解的是 buffer 對象從 CPU 流轉到 GPU 的過程,這個過程涉及到總線通信,雖然這些跟 Node.js 沒有一毛錢關係,可是其中的一些實現跟 Node.js 常見八股文面試題「跨進程通訊」有一些相同的理念。

WebGL中buffer最初被建立和寄存在CPU內存中,如何讓GPU訪問CPU內存呢?回答這個問題以前先介紹幾個基本概念:

  • CPU 的內存通常稱爲 main memory
  • GPU 本身的儲存稱爲 local memory

在 WebGL/OpenGL 中,頂點數據被建立被寄存在 main memory 中,GPU 須要獲得這部分數據進行渲染,可是 main memory 和 local memory 是絕對隔離的,不能互相訪問。

對於集成顯卡來講,GPU 和 CPU 共享總線,GPU 沒有本身獨立的儲存空間,通常是從 CPU 儲存中分配出一塊空間給 GPU 使用,咱們把這部分空間姑且叫作顯存(嚴格來講集成顯卡沒有顯存的概念)。爲了實現 GPU 和 CPU 數據的共享,業內引入了一種叫作 GART(Graphic Address Remapping Table)的技術,GART簡單說就是一個映射 main memory 和 local memory 地址的表。集成顯卡的顯存通常很小,必然是小於內存的(通常默認上限是內存總量的1/4),OS 將整個 local memory 空間映射到 main memory,維護一個 GART。此時 buffer 數據的流轉以下圖所示:

可是這套流程在獨立顯卡中是行不通的,由於獨立顯卡的顯存很是大,若是使用 GART 將顯存空間徹底映射到 CPU 內存中會佔用很是大的內存空間,32位系統的整個內存空間也就僅僅4GB,若是分出 2GB 給顯存映射,那就別幹啥了。

這下明白爲啥64位系統玩遊戲更爽了吧~

因此對於獨立顯卡須要另一套 CPU 與 GPU 的數據共享機制。目前比較廣泛的方式是在內存中單獨劃出一塊物理空間用於 CPU 和 GPU 之間的數據交換中轉,這部份內存空間叫作 pinned memory(鎖定內存)。buffer 數據首先會被從 main memory 中拷貝到 pinned memory 中,而後經過 DMA(Direct Memory Access,直接內存訪問)機制將數據傳輸到 GPU,整個過程以下:

請注意, pinned memory 是一塊物理內存而不是虛擬內存,這樣可以保證DMA的傳輸性能。

這下明白爲啥打遊戲必定要加大內存了吧~

獨立顯卡的這套數據交換機制跟 Node.js 八股文「跨進程通訊」的共享內存理念很接近,不過複雜度更高一些。

上面這些內容大都是 OpenGL 和計算機底層的機制,對 WebGL 開發者來講是無感知的,具體到涉及 Buffer 的代碼層面, WebGL 須要比 Node.js 更謹慎的處理 Buffer 的內存管理。

Node.js 中 Buffer 在分配內存時採用了 slab 預先申請、過後分配機制,這是在底層C++的邏輯,開發者不可控。這套機制可以提升 Node.js 須要頻繁申請 buffer 內存場景下的性能表現。而 WebGL 中並無這套機制,須要開發者自行處理。通常的作法是預申請一個容量很大的 buffer,而後使用 gl.bufferSubData(相似Node.js 的 Buffer.fill)局部更新數據,這樣能避免頻繁申請內存空間形成的性能損耗。

以上。

相關文章
相關標籤/搜索