美文网首页Metal
01- Metal学习之基本概念

01- Metal学习之基本概念

作者: CoderP1 | 来源:发表于2021-12-20 10:56 被阅读0次

    前言

    今天,我们口袋里装着超级计算机,iPhone正在接近我们许多笔记本电脑的计算方能力。即使拥有所有这些额外的功能,我们仍然受限于OpenGL API的限制,因为它是跨平台方案,通用性是它最大的优点,但是它无法充分利用苹果对其所有产品的深度集成。而且OpenGL 还存在一些结构性问题,导致它无法实现高效率绘制。而且每次绘制调用时发生许多昂贵的操作,Metal改变了操作顺序,并把昂贵的工作移动到绘图调用之外,这样可以释放更多的处理器带宽。而且Metal可以让程序员完全控制GPU如何进行工作,可以更高的提高效率,总结下来,OpenGL是通用图形编程API,制订了行业标准,Metal是针对苹果设备进行高度优化的图形编程API。

    Metal工作流程图

    截屏2021-12-16 下午4.43.24.png

    上图取自苹果官方文档,从这张图中,我们可以大致了解到metal的一个工作流程。
    简单来说就是渲染工作 计算工作等实际操作被封装成命令编码器,然后多个编码器打包送给命令缓冲区对象,最后命令缓冲区对象被送入命令队列中,等待交由GPU执行。

    我们先看一下Metal中常用的类


    截屏2021-12-17 下午3.13.05.png
    • MTLDevice: 对GPU硬件设备的软件引用。

    • MTLCommandQueue: 命令队列,负责创建每帧的命令缓冲对象,并管理它们。

    • MTLLibrary:包含了你的顶点着色器和片段着色器的源代码。

    • MTLRenderPipelineState: 设置绘制的信息,例如要使用的着色器函数、要使用的深度和颜色设置以及如何读取顶点数据。

    • MTLBuffer: 以可以发送到 GPU 的形式保存数据,例如顶点信息.

    下面我们来结合一个绘制三角形的例子来讲解代码流程。
    这里我们先用Metal框架来构建这个流程。您可能想知道为什么这里不直接用轮子(MetalKit),因为从最基础的Metal框架入门,可以让你对这些部件有更好的了解。而且,使用模板的话代码往往包含一些你在项目中不需要的东西,刚开始的时候,最好还是从最基础的过程做起,这样更有助于你了解这个框架。

    1.构建MTL设备

    Metal的基础是GPU,要想于GPU交互,您需要创建一个类来引用它,这里就是MTLDevice对象,它是GPU的软件抽象。

     var device = MTLCreateSystemDefaultDevice()
    

    2. 创建Metal显示图层

    func setupMetal() {
            metalLayer = CAMetalLayer.init()
            metalLayer.frame = view.bounds
            metalLayer.device = device
            metalLayer.pixelFormat = .bgra8Unorm
            metalLayer.framebufferOnly = true
            view.layer.addSublayer(metalLayer)
        }
    

    最终metal的渲染效果是呈现在CAMetalLayer类图层的。所以我们需要创建这个图层,并把它添加到视图控制器的图层中。

    3.加载数据

    func loadData() {
            
            //命令队列
            commandQueue = device?.makeCommandQueue()
            
            
            //创建顶点数据缓冲对象
            let vertexData: [Float] = [0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0]
            
            let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0])
        
            vertexBuffer = device?.makeBuffer(bytes: vertexData, length: dataSize, options: .storageModeShared)
            
            
            //加载着色器
            let defaultLibrary = device?.makeDefaultLibrary()
            
            let fragmentProgram = defaultLibrary?.makeFunction(name: "basic_fragment")
            let vertexProgram = defaultLibrary?.makeFunction(name: "basic_vertex")
            
            //创建管道状态描述器
            let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
            pipelineStateDescriptor.vertexFunction = vertexProgram
            pipelineStateDescriptor.fragmentFunction = fragmentProgram
            
            pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
            
            
            //创建管道状态对象
            do {
                try pipelineState = device?.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
            } catch let error {
                print("Failed to create pipeline state, error \(error)")
            }
        }
    

    这里我们创建一些渲染对象命令,做一些准备工作。注意:这里命令队列对象我们只需要创建一次,然后持有它就行了,因为这个创建这个对象开销很大,不需要每次渲染的时候都创建。

    4. 渲染

    这里我们没有使用模板,所以我们需要自己创建循环来渲染。这里我们采用的是CADisplaylLink类。代码如下:

    创建循环

      timer = CADisplayLink(target: self, selector: #selector(ViewController.gameLoop))
            timer.add(to: .main, forMode: .default)
    

    渲染过程

    func render() {
             @objc func gameLoop() {
            autoreleasepool {
                self.render()
            }
        }
            //创建渲染命令描述其
            let renderPassDescriptor = MTLRenderPassDescriptor()
            
            guard let drawable = metalLayer.nextDrawable() else {
                return
            }
            
            renderPassDescriptor.colorAttachments[0].texture = drawable.texture
            renderPassDescriptor.colorAttachments[0].loadAction = .clear
            renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(221.0/255.0, 160.0/255.0, 221.0/255.0, 1.0)
            
            let commandBuffer = commandQueue.makeCommandBuffer()
            
            //创建渲染命令编码器
            let renderEncoder = commandBuffer!.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
            renderEncoder?.setRenderPipelineState(pipelineState)
            renderEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0 )
            renderEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
            
            renderEncoder?.endEncoding()
            
            //将命令编码器提交到命令缓冲对象中,由命令缓冲对象提交给命令队列,等待GPU执行
            commandBuffer?.present(drawable)
            commandBuffer?.commit()
        }
    

    关于着色器,我们只简单的看一下写法,不做深入讨论,后续会介绍。

    #include <metal_stdlib>
    using namespace metal;
    
    vertex float4 basic_vertex ( const device packed_float3 * vertex_array[[buffer(0)]], unsigned int vid [[vertex_id]]) {
        return  float4 (vertex_array[vid],1.0);
    }
    
    fragment half4 basic_fragment() {
        return  half4(1.0);
        
    }
    
    

    代码已上传至Github -> Metal仓库

    相关文章

      网友评论

        本文标题:01- Metal学习之基本概念

        本文链接:https://www.haomeiwen.com/subject/xbzcfrtx.html