本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.html
今天咱們關注一下使用GPU
時的內存管理.Metal
框架將內存資源定義爲MTLBuffer
對象,它是分配的無類型,無格式的內存(任何數據類型),MTLTexture
對象則是分配的格式化內存來保存圖片數據.咱們在本文中只關注緩衝器.git
建立MTLBuffer
對象時有三種選項:github
MTLBuffer
對象並分配一塊新內存.讓咱們建立一組緩衝器,看看數據是如何被傳遞到GPU
的,及如何回傳給CPU
.咱們首先建立一塊緩衝器給輸入和輸出數據,並給它們初始化一些值:swift
let count = 1500
var myVector = [Float](repeating: 0, count: count)
var length = count * MemoryLayout< Float >.stride
var outBuffer = device.makeBuffer(bytes: myVector, length: length, options: [])
for (index, value) in myVector.enumerated() { myVector[index] = Float(index) }
var inBuffer = device.makeBuffer(bytes: myVector, length: length, options: [])
複製代碼
新的MemoryLayout< Type >.stride語法在Swift 3
被引入,來替代老的strideof(Type)
函數.同時,由於內存排列的緣由咱們用.stride
替代了.size
.stride是指針增加時移動的字節數.下一步是把咱們緩衝器告訴命令編碼器:數組
encoder.setBuffer(inBuffer, offset: 0, at: 0)
encoder.setBuffer(outBuffer, offset: 0, at: 1)
複製代碼
注意: <Metal最佳實踐指南>指出當咱們的數據小於4KB(例如一個千位的浮點數)時就避免建立緩衝器.在本例中咱們應該使用setBytes()函數來代替建立緩衝器.
性能優化
最後一步是讀取GPU
經過contents() 函數返回的數據,綁定內存數據到咱們的輸出緩衝器上:框架
let result = outBuffer.contents().bindMemory(to: Float.self, capacity: count)
var data = [Float](repeating:0, count: count)
for i in 0 ..< count { data[i] = result[i] }
複製代碼
Metal
資源必須被配置好,以便快速內存訪問和驅動器性能優化.資源的儲存模式容許咱們定義緩衝器和紋理的儲存位置和訪問權限.若是你再看一眼上面咱們建立緩衝器的地方,咱們使用了默認([])的儲存模式.ide
全部的iOS
和tvOS
設備支持unified memory model統一內存模型,它可讓CPU
和GPU
共享系統內存,而macOS
設備支持discrete memory model離散內存模型即GPU
擁有本身的內存.在iOS
和tvOS
中,Shared模式(MTLStorageModeShared
)定義了系統內存能夠被CPU
和GPU
訪問,而Private模式(MTLStorageModePrivate
)定義系統內存只能被GPU訪問.Shared
模式是全部三種操做系統中的默認儲存模式.函數
除了這兩種儲存模式外,macOS
還有一種Managed模式(MTLStorageModeManaged
),它爲一種資源定義了一對同步內存,一個副本在系統內存中,另外一個在視頻內存中來得到更快的CPU和GPU本地訪問.
如今讓咱們看看當咱們將數據緩衝器發送給GPU
時,GPU上面發生了什麼.下面是個典型的頂點着色器例子:
vertex Vertices vertex_func(const device Vertices *vertices [[buffer(0)]],
constant Uniforms &uniforms [[buffer(1)]],
uint vid [[vertex_id]])
{
...
}
複製代碼
Metal Shading Language
實現了地址空間修飾詞來指定當函數變量或參數分配時的內存區域:
read-only只讀
的.程序做用域內的變量必須被聲明爲常量地址空間,並在聲明語句中被初始化.常量地址空間爲多個實例在執行圖形或內核函數時訪問緩衝器中的同一塊位置的作了優化.做爲獎勵,讓咱們也看一下在Swift 3
中另外一種訪問內存位置的方法.這段代碼是從前面的文章The Model I/O framework中摘抄的,因此咱們就再也不講解體素的細節了.只要想着咱們須要遍歷一個數組來獲取值:
let url = Bundle.main.url(forResource: "teapot", withExtension: "obj")
let asset = MDLAsset(url: url)
let voxelArray = MDLVoxelArray(asset: asset, divisions: 10, patchRadius: 0)
if let data = voxelArray.voxelIndices() {
data.withUnsafeBytes { (voxels: UnsafePointer<MDLVoxelIndex>) -> Void in
let count = data.count / MemoryLayout<MDLVoxelIndex>.size
let position = voxelArray.spatialLocation(ofIndex: voxels.pointee)
print(position)
}
}
複製代碼
在本例中,MDLVoxelArray
對象有了個名爲spatialLocation()
的函數,它讓咱們用一個MDLVoxelIndex
類型的UnsafePointer
指針來遍歷數組,並經過每一個位置的pointee
來訪問數據.在本例中,咱們只打印出地址中的第一個值,但一個簡單的循環可讓咱們獲得全部的數,像這樣:
var voxelIndex = voxels
for _ in 0..<count {
let position = voxelArray.spatialLocation(ofIndex: voxelIndex.pointee)
print(position)
voxelIndex = voxelIndex.successor()
}
複製代碼
源代碼source code已發佈在Github上.
下次見!