1.MTLDevice
一个MTLDevice对象可以代表一个执行指令的GPU
MTLDevice 协议提供了查询设备,创建Metal其他对象的方法
(MTLCommandQueue
, MTLLibrary
, MTLRenderPipelineState
, MTLTexture
, MTLBuffer
)等
通过调用 MTLCreateSystemDefaultDevice()
方法,可以获取系统首选的设备对象。
注:MTLDevice
的创建很昂贵、耗时,并且它可以一直使用。所以只需要创建一次即可。
2.CAMetalLayer & CAMetalDrawable
负责渲染的是CALayer,UIView只要做内容的管理和事件的响应。想让UIView支持渲染Metal内容,关键在于CALayer。
CoreAnimation定义了一个CAMetakLayer
类,他的content使用Metal渲染
CAMetalDrawable
协议也是 Core Animation 中定义的,它表示某个对象是可被显示的资源。
它继承自 MTLDrawable,并扩展了一个实现 MTLTexture
协议的 texture
对象,这个 texture
用来表示渲染指令执行的目标。即之后的渲染操作,会画在这个 texture
上。
所以这个,大家要明白一点,显示的图像内容,可以抽象描述为 texture
。
3.Render
首先我们要大致了解一下渲染的流程:
GPU下发指令,渲染管线进行工作。
我们通过 MTLRenderPassDescriptor
告诉Metal下一个渲染过程中有执行什么操作
texture
:关联的纹理,即渲染目标。必须设置,不然内容不知道要渲染到哪里。
loadAction
:决定前一次 texture 的内容需要清除、还是保留
storeAction
:决定这次渲染的内容需要存储、还是丢弃
clearColor
:当 loadAction 是 MTLLoadActionClear 时,则会使用对应的颜色来覆盖当前 texture(用某一色值逐像素写入)
let renderPassDescripor = MTLRenderPassDescriptor()
renderPassDescripor.colorAttachments[0].clearColor = MTLClearColorMake(0.48, 0.74, 0.92, 1)
renderPassDescripor.colorAttachments[0].texture = drawable.texture
renderPassDescripor.colorAttachments[0].loadAction = .clear
renderPassDescripor.colorAttachments[0].storeAction = .store
4.MTLCommandQueue,MTLCommandBuffer,MTLRenderCommandEncoder
指令的提交流程:
Device 创建 Command Queue
Command Queue 创建 Command Buffer
Command Buffer 创建 Command Encoder
Command Encoder 将对应的指令编码写入 Command Encoder
Command Buffer 被提交到 GPU 中执行
GPU 执行指令后,将渲染结果写入 texture 中
展示 drawable
Command Encoder 由 Command Buffer 创建,MTLCommandBuffer 协议支持以下几种 Encoder 类型,它们被用于编码不同的 GPU 任务:
1.
MTLRenderCommandEncoder
,该类型的 Encoder 为一个 render pass 编码3D图形渲染指令。
2.MTLComputeCommandEncoder
,该类型的 Encoder 编码并行数据计算任务。
3.MTLBlitCommandEncoder
,该类型的 Encoder 支持在 buffer 和 texture 之间进行简单的拷贝操作,以及类似 mipmap 生成操作。
4.MTLParallelRenderCommandEncoder
,该类型的 Encoder 为并行图形渲染任务编码指令。
1.当前 Command Encoder 配置完毕,调用 endEncoding()
(注:只有当上一个Encoder调用endEncoding()后才能创建下一个Encoder)
2.一旦所有的编码工作结束, Command Buffer 执行 commit() 操作,标记该 Command Buffer 已经准备好被 GPU 执行。
3.然后 Command Queue 会控制什么时候执行已经提交到 Command Buffer 上的指令,执行后,会把渲染结果写入我们之前设置的渲染目的(texture)中。
4.最后,我们需要把渲染结果,显示到屏幕上。这时候需要调用 drawable 对象的 present() 方法,才会将对应的显示操作提交到 Core Animation 上去。为了确保 drawable 上已经渲染完毕,我们一般是使用 Command Buffer 的 present(_ drawable: MTLDrawable) 方法,它会在 Command Buffer 确保已经被 GPU 执行后,再自动调用 drawable 对象的 present() 方法。
5.MTLRenderPipelineState & MTLRenderPipelineDescriptor
渲染管线的大致流程:
我们上面所有的准备工作都做好了,然而GPU并不知道我们要用到什么东西,需要我们按照它能识别的方式告诉它。
我们渲染管线对象在不同阶段需要:顶点数据,顶点颜色,片段着色器进行描述
我们通过MTLRenderPipelineDescriptor
来描述创建MTLRenderPipelineState
let library = device?.makeDefaultLibrary()
let vertexFunction = library?.makeFunction(name: "vertexShader")
let fragmentFunction = library?.makeFunction(name: "fragmentShader")
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineState = try! device?.makeRenderPipelineState(descriptor: pipelineDescriptor)
6.MTLBuffer
MTLBuffer,可以理解成一个 CPU 和 GPU 都可以访问的内存块,它里面存储的数据,是没有格式、类型限制的,即可以存储任意类型的数据。
我们一般使用MTLBuffer来存储顶点数据
我们通过device来创建维护MTLBuffer对象,管理我们的数据
/**
@method newBufferWithBytes:length:options:
@brief Create a buffer by allocating new memory and specifing the initial contents to be copied into it.
*/
func makeBuffer(bytes pointer: UnsafeRawPointer, length: Int, options: MTLResourceOptions = []) -> MTLBuffer?
----------------------------------------------------------
let vertices = [
XTVertex(position: [-1, -1], textureCoordinates: [0.0, 1.0]),
XTVertex(position: [-1, 1], textureCoordinates: [0.0, 0.0]),
XTVertex(position: [1, -1], textureCoordinates: [1.0, 1.0]),
XTVertex(position: [1, 1], textureCoordinates: [1.0, 0.0]),
]
vertexBuffer = device?.makeBuffer(bytes: vertices, length: MemoryLayout.size * 4, options: .cpuCacheModeWriteCombined)
了解一下 MTLResourceOptions
,表示资源的管理方式
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 }
}
我们使用的cpuCacheModeWriteCombined
,会优化资源,表示CPU只能写入。
一般情况,也可以不设置,不设置就是用默认MTLResourceCPUCacheModeDefaultCache
,表示CPU,GPU都能正常读写操作
7.MTLTexture
纹理是我们经常用到的。我们现实纹理一般都需要这些操作
· 图片转纹理
· 纹理映射,采样,从纹理上获取具体的色值
代码如下:
func newTexture(_ image: UIImage) -> MTLTexture {
let imageRef = image.cgImage!
let width = imageRef.width
let height = imageRef.height
let colorSpace = CGColorSpaceCreateDeviceRGB() //色域
let rawData = calloc(height * width * 4, MemoryLayout.size) //图片存储数据的指针
let bitsPerComponent = 8 //指定每一个像素中组件的位数(bits,二进制位)。例如:对于32位格式的RGB色域,你需要为每一个部分指定8位
let bytesPerPixel = 4
let bytesPerRow = width * bytesPerPixel
let context = CGContext(data: rawData,
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)
context?.draw(imageRef, in: CGRect(x: 0, y: 0, width: width, height: height))
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: width, height: height, mipmapped: false)
let texture = device?.makeTexture(descriptor: textureDescriptor)
let region = MTLRegionMake2D(0, 0, width, height)
texture?.replace(region: region, mipmapLevel: 0, withBytes: rawData!, bytesPerRow: bytesPerRow)
free(rawData)
return texture!
}
/*!
MTLRegion 结构体,定义了 texture 中对应的图像区域,我们一般和图像的实际大小保持一致即可。
@struct MTLRegion
@abstract Identify a region in an image or texture.
*/
public struct MTLRegion {
public var origin: MTLOrigin
public var size: MTLSize
public init()
public init(origin: MTLOrigin, size: MTLSize)
}
针对2D纹理图像的纹理坐标如下:
来自:小专栏-iOS图像处理
此文章只为自己做记录,勉励自己学习。
热爱生活,记录生活!