# Metal 学习(一)

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

首先我们要大致了解一下渲染的流程:


image.png

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

image.png

指令的提交流程:
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

渲染管线的大致流程:


image.png

我们上面所有的准备工作都做好了,然而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纹理图像的纹理坐标如下:


image.png

来自:小专栏-iOS图像处理

此文章只为自己做记录,勉励自己学习。

热爱生活,记录生活!

你可能感兴趣的:(# Metal 学习(一))