01- Metal學(xué)習(xí)之基本概念

前言

今天,我們口袋里裝著超級計算機,iPhone正在接近我們許多筆記本電腦的計算方能力。即使擁有所有這些額外的功能,我們?nèi)匀皇芟抻贠penGL API的限制,因為它是跨平臺方案,通用性是它最大的優(yōu)點,但是它無法充分利用蘋果對其所有產(chǎn)品的深度集成。而且OpenGL 還存在一些結(jié)構(gòu)性問題,導(dǎo)致它無法實現(xiàn)高效率繪制。而且每次繪制調(diào)用時發(fā)生許多昂貴的操作,Metal改變了操作順序,并把昂貴的工作移動到繪圖調(diào)用之外,這樣可以釋放更多的處理器帶寬。而且Metal可以讓程序員完全控制GPU如何進行工作,可以更高的提高效率,總結(jié)下來,OpenGL是通用圖形編程API,制訂了行業(yè)標準,Metal是針對蘋果設(shè)備進行高度優(yōu)化的圖形編程API。

Metal工作流程圖

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

上圖取自蘋果官方文檔,從這張圖中,我們可以大致了解到metal的一個工作流程。
簡單來說就是渲染工作 計算工作等實際操作被封裝成命令編碼器,然后多個編碼器打包送給命令緩沖區(qū)對象,最后命令緩沖區(qū)對象被送入命令隊列中,等待交由GPU執(zhí)行。

我們先看一下Metal中常用的類


截屏2021-12-17 下午3.13.05.png
  • MTLDevice: 對GPU硬件設(shè)備的軟件引用。

  • MTLCommandQueue: 命令隊列,負責創(chuàng)建每幀的命令緩沖對象,并管理它們。

  • MTLLibrary:包含了你的頂點著色器和片段著色器的源代碼。

  • MTLRenderPipelineState: 設(shè)置繪制的信息,例如要使用的著色器函數(shù)、要使用的深度和顏色設(shè)置以及如何讀取頂點數(shù)據(jù)。

  • MTLBuffer: 以可以發(fā)送到 GPU 的形式保存數(shù)據(jù),例如頂點信息.

下面我們來結(jié)合一個繪制三角形的例子來講解代碼流程。
這里我們先用Metal框架來構(gòu)建這個流程。您可能想知道為什么這里不直接用輪子(MetalKit),因為從最基礎(chǔ)的Metal框架入門,可以讓你對這些部件有更好的了解。而且,使用模板的話代碼往往包含一些你在項目中不需要的東西,剛開始的時候,最好還是從最基礎(chǔ)的過程做起,這樣更有助于你了解這個框架。

1.構(gòu)建MTL設(shè)備

Metal的基礎(chǔ)是GPU,要想于GPU交互,您需要創(chuàng)建一個類來引用它,這里就是MTLDevice對象,它是GPU的軟件抽象。

 var device = MTLCreateSystemDefaultDevice()

2. 創(chuàng)建Metal顯示圖層

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

最終metal的渲染效果是呈現(xiàn)在CAMetalLayer類圖層的。所以我們需要創(chuàng)建這個圖層,并把它添加到視圖控制器的圖層中。

3.加載數(shù)據(jù)

func loadData() {
        
        //命令隊列
        commandQueue = device?.makeCommandQueue()
        
        
        //創(chuàng)建頂點數(shù)據(jù)緩沖對象
        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")
        
        //創(chuàng)建管道狀態(tài)描述器
        let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
        pipelineStateDescriptor.vertexFunction = vertexProgram
        pipelineStateDescriptor.fragmentFunction = fragmentProgram
        
        pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        
        
        //創(chuàng)建管道狀態(tài)對象
        do {
            try pipelineState = device?.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
        } catch let error {
            print("Failed to create pipeline state, error \(error)")
        }
    }

這里我們創(chuàng)建一些渲染對象命令,做一些準備工作。注意:這里命令隊列對象我們只需要創(chuàng)建一次,然后持有它就行了,因為這個創(chuàng)建這個對象開銷很大,不需要每次渲染的時候都創(chuàng)建。

4. 渲染

這里我們沒有使用模板,所以我們需要自己創(chuàng)建循環(huán)來渲染。這里我們采用的是CADisplaylLink類。代碼如下:

創(chuàng)建循環(huán)

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

渲染過程

func render() {
         @objc func gameLoop() {
        autoreleasepool {
            self.render()
        }
    }
        //創(chuàng)建渲染命令描述其
        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()
        
        //創(chuàng)建渲染命令編碼器
        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執(zhí)行
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
    }

關(guān)于著色器,我們只簡單的看一下寫法,不做深入討論,后續(xù)會介紹。

#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倉庫

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容