1.对比
在Metal(2)——三角形案例中,我们使用了下面的方法来向顶点着色器传递数据:
func setVertexBytes(_ bytes: UnsafeRawPointer,
length: Int,
index: Int)
在苹果的官方文档中,说明此方法避免了创建用于存储数据的缓冲区的开销,由Metal来自行管理。但实际上,setVertexBytes:length:atIndex: 这个方法,其实是每次都重新 copy 传递的数据,创建一个 MTLBuffer 对象,然后改用 setVertexBuffer(_:offset:index:) 或者 setVertexBuffers(_:offsets:with:) 方法,来传递数据给顶点着色器,然后删掉旧的 MTLBuffer 对象。所以如果是频繁使用的数据,使用这个方法,还是会造成不必要的性能损耗。
文档中同时还提到了,这个方法只能一次传递长度小于4k的数据。
所以,如果数据长度大于4K或者是会多次使用的数据,还是建议使用MTLBuffer 对象。
2.MTLBuffer
2.1定义
MTLBuffer,可以理解成一个 CPU 和 GPU 都可以访问的内存块,它里面存储的数据,是没有格式、类型限制的,即可以存储任意类型的数据。
Metal 对于MTLBuffer中存储的内容一无所知,只知道它的长度大小。我们需要自己来定义存储在MTLBuffer对象中的数据格式,并确保app跟着色器知道如何读取和写入数据。
2.2创建
MTLBuffer对象只能通过MTLDevice对象来创建。苹果提供了下面三种方法来创建:
-
makeBuffer(length:options:)创建MTLBuffer对象,并分配指定长度的存储空间。。 -
makeBuffer(bytes:length:options:)MTLBuffer创建MTLBuffer对象,分配指定长度的存储空间,并将数据拷贝到开辟的空间中。 -
makeBuffer(bytesNoCopy:length:options:deallocator:)创建一个MTLBuffer对象,该对象重用现有的存储分配,并且不分配任何新的存储。
2.3 Options
在创建MTLBuffer对象时,还需要设置一个属性——MTLResourceOptions, 它表示着资源(Buffer、Texture..)的管理方式,要根据项目实际情况取舍。一般情况下,也可以不写(options:[]),它表示 CPU,GPU 都能正常读写。
@available(iOS 8.0, *)
public struct MTLResourceOptions : OptionSet {
public init(rawValue: UInt)
public static var cpuCacheModeWriteCombined: MTLResourceOptions { get }
@available(iOS 9.0, *)
public static var storageModeShared: MTLResourceOptions { get }
@available(iOS 9.0, *)
public static var storageModePrivate: MTLResourceOptions { get }
@available(iOS 10.0, *)
public static var storageModeMemoryless: MTLResourceOptions { get }
@available(iOS 10.0, *)
public static var hazardTrackingModeUntracked: MTLResourceOptions { get }
@available(iOS 13.0, *)
public static var hazardTrackingModeTracked: MTLResourceOptions { get }
public static var optionCPUCacheModeWriteCombined: MTLResourceOptions { get }
}
3.案例
下面将用一个小案例,来讲解如何使用MTLBuffer
项目的结构与流程跟之前的非常类似。
创建generateVertexData方法,来创建并返回大量的顶点数据。
func generateVertexData() -> [ZVertex]{
let quadVertices = [ZVertex(position: [-20, 20], color: [1, 0, 0, 1]),
ZVertex(position: [20, 20], color: [1, 0, 0, 1]),
ZVertex(position: [-20, -20], color: [1, 0, 0, 1]),
ZVertex(position: [20, -20], color: [0, 0, 1, 1]),
ZVertex(position: [-20, -20], color: [0, 0, 1, 1]),
ZVertex(position: [20, 20], color: [0, 0, 1, 1]),
]
//行/列 数量
let NUM_COLUMNS = 25
let NUM_ROWS = 15
//顶点个数
let NUM_VERTICES_PER_QUAD = quadVertices.count
//四边形间距
let QUAD_SPACING:Float = 50.0
//2. 开辟空间
var vertices = [ZVertex]()
//3.获取顶点坐标(循环计算)
for row in 0..<NUM_ROWS {
for column in 0..<NUM_COLUMNS {
//左上角的位置 注意坐标系基于2D笛卡尔坐标系,中心点(0,0),所以会出现负数位置
let x = (Float(-NUM_COLUMNS)/2 + Float(column)) * QUAD_SPACING + QUAD_SPACING/2.0
let y = (Float(-NUM_ROWS)/2 + Float(row)) * QUAD_SPACING + QUAD_SPACING/2.0
let upperLeftPosition = vector2(x, y)
//遍历6个顶点 即小矩形顶点
for index in 0..<NUM_VERTICES_PER_QUAD {
var vertex = quadVertices[index]
//向量的加法
vertex.position += upperLeftPosition
let newVertex = ZVertex(position: vertex.position, color: vertex.color)
//新的顶点数据 追加到数组
vertices.append(newVertex)
}
}
}
//最后返回
return vertices
}
通过创建的顶点数据,来创建MTLBuffer对象
//5.获取顶点数据
let verteices = generateVertexData()
//创建一个vertex buffer,可以由GPU来读取
vertexBuffer = device?.makeBuffer(bytes: verteices,
length: MemoryLayout<ZVertex>.size*verteices.count,
options: [])
使用MTLBuffer对象,将顶点数据传送到顶点着色器。
//将_vertexBuffer 设置到顶点缓存区中
renderEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: Int(ZVertexInputIndexVertices.rawValue))