[MetalKit]35-Working-with-memory-in-Metal-part-2記憶體管理2

蘋果API搬運工發表於2017-12-14

本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.

MetalKit系統文章目錄


在深入記憶體管理時有很多話題需要探討.上次我們已經瞭解了建立MTLBuffer物件有三種選項設定:用新資料分配一塊新記憶體,用已存在區域複製資料到一塊新記憶體,重用一塊已經存在的分配區不復制資料.因為我們以前並不關注記憶體,就讓我們驗證一下證明這確實是真的.首先我們複製資料到另一分配區:

let count = 2000
let length = count * MemoryLayout< Float >.stride
var myVector = [Float](repeating: 0, count: count)
let myBuffer = device.makeBuffer(bytes: myVector, length: length, options: [])
withUnsafePointer(to: &myVector) { print($0) }
print(myBuffer.contents())
複製程式碼

注意: withUnsafePointer() 函式提供給我們的實際資料的記憶體地址在堆上,而不是指向資料的指標(在棧上)的地址.

你的輸出看起來會像這樣:

0x000000010043e0e0
0x0000000102afd000
複製程式碼

注意到上面地址的最後三位數字了嗎?這是來自於頁對齊資料,因為地址是以0 mod pageSize確定的,因為最後三位是0,因為我們的頁尺寸是0x1000.

現在我們接著看Storage Modes,我們上次曾簡短提到過.至少需要記住四條主要規則,每種儲存模式一條:

Mode Description
Shared macOS緩衝器,iOS/tvOS資源上為預設;masOS紋理上不可用.
Private 主要用於資料只被GPU訪問的情況下
Memoryless 僅用於iOS/tvOS晶片的臨時渲染目標(紋理).
Managed macOS紋理的預設模式;在iOS/tvOS資源上不可用.

對於一個更好的大圖片,下面是完整的作弊表,讓你無需記憶上面的規則,更容易使用:

storage-modes.png

最複雜的情況是,在當masOS處理緩衝器時,資料需要同時被CPUGPU訪問.我們選擇儲存模式時,是基於下面一個或多個條件為真來決定的:

  • Private - 對於最多隻改變一次的大尺寸資料,那它就不是"髒"的.建立一個Shared模式的源緩衝器,然後位塊傳送它的資料到一個Private模式的目標緩衝器中.在本例中資源一致不是必須的,因為資料只被GPU訪問.該操作是花費最低的(一個一次性花費).
  • Managed - 對於很少改變(每幾幀)的中等尺寸資料,它就是部分"髒"的.一個資料副本儲存在系統記憶體中給CPU使用,另一份儲存在GPU記憶體中.資源一致性通過同步兩份副本來嚴格控制.
  • Shared - 對於每幀都更新的小尺寸資料,那它就是完全"髒"的.資料存放在系統記憶體中,並同時對CPUGPU可見,可修改.資源一致性只在命令緩衝器界限內被保證.

如何保證資源的一致性?首先,確保所有的來自CPU的修改已經在命令緩衝器被提交(檢查命令緩衝器狀態屬性是否是MTLCommandBufferStatusCommitted)之前完成了.在GPU結束執行命令緩衝器後,CPU只應該在GPU發訊號給CPU,告知CPU命令緩衝器結束執行(檢查命令緩衝器狀態屬性是否是MTLCommandBufferStatusCompleted)之後,再開始著手修改.

最後,讓我們看看masOS的資源是如何同步完成的.對於緩衝器:在CPU寫入後使用didModifyRange() 將修改項通知GPU,這樣Metal可以只更新這個資料區域; 在GPU寫入後,在一個位塊傳送操作內,用synchronize(resource:) 來重新整理快取,這樣CPU就可以訪問更新後的資料. 對於紋理:在CPU寫入後,使用兩個replace() 區域函式中的一個,將修改項通知GPU,這樣Metal可以只更新這個資料區域;在GPU寫入後,在一個位塊傳送操作內,使用兩個synchronize() 函式中的一個,來允許MetalGPU結束脩改資料後再更新系統快取副本.

原始碼source code已釋出在Github上.

下次見!

相關文章