在前面數據綁定基本流程,簡單說了下,在Axiom中,數據從我們C#的托管環境到下面的OpenGL或是D3D的非托管環境,有個轉化過程,相關實現我們可以從BufferBase看起.BufferBase與他的子類集合了相關數據塊在托管環境與非托管環境的相關操作. 在BufferBase中,包裝了一個重要的類GCHandle,其中托管代碼中,因為GC的關系,在運行時所有元素的內存地址也都是變化的,這里在我們需要用到GCHandleType.Pinned,這個會固定我們申請的空間地址,防止GC移動與釋放這塊內存,當然這塊內存的回收也和C++里一樣,需要我們手動調用Free方法去釋放.GCHandle對象保存了數據塊在內存中的位置,而GCHandle里的Ptr(int)保存的是我們在GCHandle內存塊里的位置(如申請100字節長度,那么Ptr只能是0-99這100百個數),默認是頭0開始,一般用來做偏移地址運算.BufferBase提供一個虛擬方法Pin(),供子類提供數據塊固定的指針地址,以便於OpenGL,D3D中非托管環境調用.最后BufferBase里的方法Wrap,用來直接生成一個UnsafeBuffer的對象.
下面針對UnsafeBuffer,我們來先看一段代碼:
這段代碼里,我們首先用GCHandle.Alloc申請一塊buffer大小的空間,在這buffer雖然是object類型,但是因為我們申請的是固定的句柄(GCHandleType.Pinned),這個是不支持引用類型的,就是buffer必需是值類型(值類型也有要求,應該說是准確聲明空間長度的值類型),不過在Axiom中,我們一般是傳遞如int[],float[]這種,所以此處滿足.申請后,我們用方法AddrofPinnedObject得到用Pinned方式申請的指針,上面this.Buf是成byte*塊,就是字節塊,這樣主要是為了好做相關地址運算,如下面我傳入一個-100.0,-100.0,0.0...的數組,請看下面相關結果.
表達式 |
內存地址 |
地址求值 |
(float*)this.Buf |
0x01fdfab4 |
*((float*)this.Buf): -100.0 |
(float*)this.Buf + 1 |
0x01fdfab8 |
*((float*)this.Buf + 1): -100.0 |
(float*)this.Buf + 2 |
0x01fdfabc |
*((float*)this.Buf + 2): 0.0 |
(float*)(this.Buf+4) |
0x01fdfab8 |
*((float*)(this.Buf+4)): -100.0 |
(float*)(this.Buf+8) |
0x01fdfabc |
*((float*)(this.Buf+8)): 0.0 |
我們知道內存中,數據全是0101這樣的bit位,我們的數組buffer保存在內存中也是這樣,我們平常定義的什么int,float,byte等只是為了讓計算機正確解析位來轉換成相應數據.這里轉換成byte*,float*,int*的區別也是這樣,如果轉化成byte位,那么他加1就是一個byte,同理,他轉化成int,float,他就加4個byte,也就是4個字節長度.我們知道sizeof計算的長度就是字節來算的,這里轉化成byte*就是用了容易對地址進行定義,因為字節是我們數據塊用到的最小單位,對應屬性Length長度指的也是字節.如上,轉化成byte*后,我要得到第二個float就用(float*)(this.Buf+4)或是(float*)this.buf+1,效果一樣.我們再來看下面一段代碼就很清楚了.
我們知道,long是8個字節,而index是指單位為字節的長度,那么轉化成對應的long數組里對應的long的索引應該是index*8,例如index是10,那么long數組對應值應該是對應字節塊的初地址加上80的偏移量.對應的如ITypePointer<byte>,ITypePointer<short>,ITypePointer<int,float>,ITypePointer<double>分別是index,index*2,index*4,index*8.
而同屬BufferBase的子類的ManagedBuffer,不同於UnsafeBuffer管理的byte*,他管理的是byte[],好吧,這二個也是一樣,相關的索引器也是在對byte索引的偏移計算,同UnsafeBuffer邏輯一樣,不同的是,UnsafeBuffer中的GCHandle.Alloc操作是在初始化的時候.而ManagedBuffer是發生在調用方法Pin時,才會執行GCHandle.Alloc操作.注意,不管是UnsafeBuffer還是ManagedBuffer,Pin方法指向的指針位置是當前內存塊的頭地址(GCHandle.AddrofPinnedObject())加上偏移地址(BufferBase.Ptr).
同樣如ManagedBufferCol3b等,都是在原來類的基礎上增加一個對應的索引器.
在前面Axiom3D:數據綁定基本流程可以看到頂點數據(VertexData),索引數據(IndexData),他們都有HardwareBuffer對象,這個對象用來與BufferBase互動.HardwareBuffer與BufferBase的關系有些類似AnimationTrack與KeyFrame,他們的具體實現交給子類,抽象互動都由本身來實現.前面我們說了BufferBase,主要是提供一個數據塊空間,這個空間的位置,對這個空間的索引,相關子類對應托管環境與非托管環境的實現.而HardwareBuffer如下方法WriteData,ReadData,Copy分別提供對BufferBase的寫入,讀取,復制數據一些操作的包裝.針對GL的環境,我們來看下HardwareBuffer的子類GLHardwareVertexBuffer,其WriteData,ReadData就是調用glBufferData,glGetBufferData分別把BufferBase里的數據塊寫入到顯存,或從顯存中讀取出來.不用顯存,DefaultHardwareVertexBuffer提供針對內存中BufferBase的相關操作,不像GLHardwareVertexBuffer需要把數據從CPU到GPU的過程,DefaultHardwareVertexBuffer里的讀取,寫入,復制操作就是把BufferBase的數據塊從一個地方復制到另一個地方,直接用的就是對應的BufferBase的Copy操作.
還有一個地方,數據也是一塊一塊的包裝在一個位置的,就是紋理,我們知道紋理圖片里的每個像素點都是各個類型的RGBA元素組合而成,那么在我們內存或顯存中,圖片就是一個數據塊區域,知道了這個數據塊區域的格式,如RGBA8B,那么我們就能正確的解析成相應的像素點集合,然后就能正確的在顯示器上顯示成對應圖像了.通過紋理我們能利用GPU強大的並行功能,因為紋理就是數據塊,就是數組.具體可以參看GPGPU基本運算與乒乓技術.
如同頂點對應HardwareVertexBuffer,頂點索引對應HardwareIndexBuffer,圖片也有自己的HardwareBuffer對應類HardwarePixelBuffer.在介紹這個類時,先看下類BasicBox與PixelBox,其中PixelBox是BasicBox的子類,BasicBox差不多就是針對一個描述一個立方體,有6面,分別是前后左右上下6面(一般情況下,默認寬度Width指左右距離,高度Height指上下距離,深度Depth指前后距離).而PixelBox描述的是圖片(包含一維至三維,一維用寬度Width,二維增加高度Height,三維增加深度Depth).在BasicBox的上面增加一個BufferBase用來表示像素數據,其中PixelBox有個比較重要的屬性PixelFormat,其實這個類就相當於VertexDeclaration,一個是指明像素是如何組成的,如SHORT_RBG.而VertexDeclaration指明頂點是如何組成,如T2fN3fV3f.有了PixelFormat,我們才能正確序列化PixelBox里的數據塊BufferBase里的信息.在PixelConverter這個類中提供了二個PixelBox互相操作的方法,例如從一張圖片中提取他的R通道數據,調用PixelConverter的BulkPixeConversion方法就可.有興趣可以看一下具體實現,本質還是對BufferBase根據PixelFormat與索引得到偏移賦值操作.
而HardwarePixelBuffer提供二個抽象方法,一個是BlitFormMemory,把PixelBox中的像素數據取出放到新的內存或顯存.另一個是BlitToMemory,把顯存和內存里的數據取出.這二個函數分別在GL的環境中的子類GLHardwareBuffer與他的子類GLTextureBuffer中的實現如下,BlitFormMemory就是調用GL的API如glTexImage2D這類把數據,而BlitToMemory就是調用glGetTexImage,這樣就很容易理解了.
下圖是上面類的關系,突出像素HardwarePixelBuffer與BufferBase的關系。
