Metal学习(2) - 创建一个MetalKit视图和一个渲染通道来绘制视图的内容

597 阅读2分钟
import UIKit
import MetalKit

class ViewController: UIViewController {

    // // 保证render与metalView使用的是同一个MTLDevice
    let device = MTLCreateSystemDefaultDevice()

    lazy var metalView: MTKView = {
        let view = MTKView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        // 控制是否只在需要更新内容时才重新绘制, 默认false,每一帧都重新绘制
        view.enableSetNeedsDisplay = true
        // 设置MTLDevice
        view.device = device
        // 清屏颜色(参照OpenGL)
        view.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
        // MTKView的渲染控制必须要有一个符合MTKViewDelegate的代理实现
        view.delegate = self.render
        return view
    }()

    lazy var render: MetalRender = {
        // 传入MTLDevice,来生成命令队列
        let render = MetalRender(device)
        return render
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(metalView)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        metalView.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
    }
}

view.enableSetNeedsDisplay = true合理的使用这个,可以提高性能。

import UIKit
import MetalKit

class MetalRender: NSObject {

    var commandQueue: MTLCommandQueue?

    private override init() {
        super.init()
    }

    convenience init(_ device: MTLDevice?) {
        self.init()
        // 生成命令队列
        self.commandQueue = device?.makeCommandQueue()
    }
}

extension MetalRender: MTKViewDelegate {
    // MTKViewDelegate必须实现的方法
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        // 每当视图改变方向或调整大小时调用
        print("mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize)")
    }
    // MTKViewDelegate必须实现的方法
    func draw(in view: MTKView) {
        // 绘制内容
        print("draw(in view: MTKView)")
        // 必须获取到MTLRenderPassDescriptor才行
        // MTLRenderPassDescriptor描述了呈现目标的集合,以及在呈现通道的开始和结束时应该如何处理它们。
        // MTLRenderPassDescriptor还定义了渲染的其他方面,这些方面不属于本示例的一部分。
        guard let renderPassDescriptor = view.currentRenderPassDescriptor else {
            print("view.currentRenderPassDescriptor == nil")
            return
        }

        let commandBuffer = commandQueue?.makeCommandBuffer()
        let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
        // 这个示例中,没有编码任何绘制命令,所以渲染通道所做的唯一事情就是擦除纹理。调用编码器的endEncoding方法以指示传递完成。
        commandEncoder?.endEncoding()

        // 绘制纹理不会自动在屏幕上显示新的内容。事实上,只有一些纹理可以呈现在屏幕上。
        // 在Metal中,可以在屏幕上显示的纹理是由可绘制对象MTLDrawable管理的,为了显示内容,您可以呈现可绘制对象。
        // MTKView自动创建可绘制的对象来管理它的纹理。读取currentDrawable属性以获取drawable对象,该对象拥有渲染通道的目标纹理。
        // 视图返回一个CAMetalDrawable对象,一个连接到Core Animation的对象。
        if let drawable = view.currentDrawable {
        // 这个方法告诉Metal,当命令缓冲区被调度执行时,Metal应该与Core Animation协调,在渲染完成后显示纹理。
        // 当Core Animation呈现纹理时,它成为视图的新内容。
        // 在这个示例中,这意味着擦除的纹理成为视图的新背景。
        // 这一变化与Core Animation为屏幕用户界面元素所做的任何其他视觉更新一起发生。
            commandBuffer?.present(drawable)
        }
        // 提交命令缓冲区
        commandBuffer?.commit()
    }
}

运行输出:

draw(in view: MTKView)

image.png

点击后输出:

mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize)

image.png