版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.12 星期五 |
前言
很多做視頻和圖像的,相信對這個框架都不是很陌生,它渲染高級3D圖形赶舆,并使用GPU執(zhí)行數(shù)據(jù)并行計算信峻。接下來的幾篇我們就詳細的解析這個框架倦青。感興趣的看下面幾篇文章。
1. Metal框架詳細解析(一)—— 基本概覽
2. Metal框架詳細解析(二) —— 器件和命令(一)
3. Metal框架詳細解析(三) —— 渲染簡單的2D三角形(一)
4. Metal框架詳細解析(四) —— 關(guān)于GPU Family 4(一)
5. Metal框架詳細解析(五) —— 關(guān)于GPU Family 4之關(guān)于Imageblocks(二)
6. Metal框架詳細解析(六) —— 關(guān)于GPU Family 4之關(guān)于Tile Shading(三)
7. Metal框架詳細解析(七) —— 關(guān)于GPU Family 4之關(guān)于光柵順序組(四)
8. Metal框架詳細解析(八) —— 關(guān)于GPU Family 4之關(guān)于增強的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細解析(九) —— 關(guān)于GPU Family 4之關(guān)于線程組共享(六)
10. Metal框架詳細解析(十) —— 基本組件(一)
11. Metal框架詳細解析(十一) —— 基本組件之器件選擇 - 圖形渲染的器件選擇(二)
12. Metal框架詳細解析(十二) —— 基本組件之器件選擇 - 計算處理的設(shè)備選擇(三)
13. Metal框架詳細解析(十三) —— 計算處理(一)
14. Metal框架詳細解析(十四) —— 計算處理之你好盹舞,計算(二)
15. Metal框架詳細解析(十五) —— 計算處理之關(guān)于線程和線程組(三)
16. Metal框架詳細解析(十六) —— 計算處理之計算線程組和網(wǎng)格大胁洹(四)
17. Metal框架詳細解析(十七) —— 工具、分析和調(diào)試(一)
18. Metal框架詳細解析(十八) —— 工具踢步、分析和調(diào)試之Metal GPU Capture(二)
19. Metal框架詳細解析(十九) —— 工具癣亚、分析和調(diào)試之GPU活動監(jiān)視器(三)
20. Metal框架詳細解析(二十) —— 工具、分析和調(diào)試之關(guān)于Metal著色語言文件名擴展名贾虽、使用Metal的命令行工具構(gòu)建庫和標記Metal對象和命令(四)
21. Metal框架詳細解析(二十一) —— 基本課程之基本緩沖區(qū)(一)
22. Metal框架詳細解析(二十二) —— 基本課程之基本紋理(二)
23. Metal框架詳細解析(二十三) —— 基本課程之CPU和GPU同步(三)
24. Metal框架詳細解析(二十四) —— 基本課程之參數(shù)緩沖 - 基本參數(shù)緩沖(四)
25. Metal框架詳細解析(二十五) —— 基本課程之參數(shù)緩沖 - 帶有數(shù)組和資源堆的參數(shù)緩沖區(qū)(五)
26. Metal框架詳細解析(二十六) —— 基本課程之參數(shù)緩沖 - 具有GPU編碼的參數(shù)緩沖區(qū)(六)
27. Metal框架詳細解析(二十七) —— 高級技術(shù)之圖層選擇的反射(一)
28. Metal框架詳細解析(二十八) —— 高級技術(shù)之使用專用函數(shù)的LOD(一)
29. Metal框架詳細解析(二十九) —— 高級技術(shù)之具有參數(shù)緩沖區(qū)的動態(tài)地形(一)
30. Metal框架詳細解析(三十) —— 延遲照明(一)
31. Metal框架詳細解析(三十一) —— 在視圖中混合Metal和OpenGL渲染(一)
開始
首先看一下寫作環(huán)境
Swift 4, iOS 11, Xcode 9
在本教程中逃糟,您將深入了解渲染管道并創(chuàng)建一個呈現(xiàn)紅色立方體的Metal應(yīng)用程序。 在此過程中蓬豁,您將發(fā)現(xiàn)所有負責(zé)拍攝3D物體并將其轉(zhuǎn)換為您在屏幕上看到的華麗像素的硬件芯片绰咽。
The GPU and the CPU - GPU和CPU
所有計算機都有一個中央處理單元(CPU),用于驅(qū)動操作并管理計算機上的資源地粪。他們還有一個圖形處理單元(Graphics Processing Unit (GPU))
取募。
GPU是一種專用硬件組件,可以非丑〖迹快速地處理圖像玩敏,視頻和大量數(shù)據(jù)斗忌。這稱為吞吐量(throughput)
。吞吐量通過在特定時間單位中處理的數(shù)據(jù)量來度量旺聚。
另一方面织阳,CPU無法快速處理大量數(shù)據(jù),但它可以非撑榇猓快速地處理許多順序任務(wù)(一個接一個)唧躲。處理任務(wù)所需的時間稱為延遲(latency)
。
理想的設(shè)置包括低延遲和高吞吐量碱璃。低延遲允許串行執(zhí)行排隊任務(wù)弄痹,因此CPU可以執(zhí)行命令,而不會使系統(tǒng)變慢或無響應(yīng)嵌器;高吞吐量讓GPU可以異步渲染視頻和游戲而不會拖延CPU肛真。由于GPU具有高度并行化的架構(gòu),專門用于重復(fù)執(zhí)行相同的任務(wù)爽航,并且很少或沒有數(shù)據(jù)傳輸蚓让,因此能夠處理更大量的數(shù)據(jù)。
下圖顯示了CPU和GPU之間的主要差異讥珍。
CPU具有大容量高速緩存和少量算術(shù)邏輯單元(Arithmetic Logic Unit - ALU)
內(nèi)核凭疮。 CPU上的低延遲高速緩存用于快速訪問臨時資源。 GPU沒有太多的高速緩沖存儲器串述,并且有更多ALU核心的空間执解,它們只進行計算而不將部分結(jié)果保存到存儲器中。
此外纲酗,CPU通常只有少數(shù)內(nèi)核衰腌,而GPU有數(shù)百甚至數(shù)千個內(nèi)核。通過更多核心觅赊,GPU可以將問題分解為許多較小的部分右蕊,每個部分并行運行在單獨的核心上,從而隱藏延遲吮螺。在處理結(jié)束時饶囚,將部分結(jié)果合并,并將最終結(jié)果返回給CPU鸠补。但核心并不是唯一重要的事情萝风!
除了精簡之外,GPU內(nèi)核還具有用于處理幾何結(jié)構(gòu)的特殊電路紫岩,通常稱為著色器內(nèi)核(shader cores)
规惰。這些著色器核心負責(zé)您在屏幕上看到的美麗色彩。 GPU一次寫入整個幀以適合整個渲染窗口泉蝌。然后歇万,它將繼續(xù)盡快渲染下一幀以保持良好的幀速率揩晴。
CPU繼續(xù)向GPU發(fā)出命令以使其保持忙碌,但在某些時候贪磺,CPU將完成發(fā)送命令或GPU將完成處理它收到的命令硫兰。 為了避免停止,CPU上的Metal
會在命令緩沖區(qū)中排隊多個命令寒锚,并按順序為下一幀發(fā)出新命令瞄崇,而不必等待GPU完成第一幀。 這樣壕曼,無論誰先完成工作,都會有更多的工作要做等浊。
一旦接收到所有命令和資源腮郊,圖形管道的GPU部分就會啟動。
The Metal Project - Metal工程
您一直在使用Playgrounds
來了解Metal
筹燕。 Playgrounds
非常適合測試和學(xué)習(xí)新概念轧飞。 了解如何設(shè)置完整的Metal項目非常重要。 由于iOS模擬器不支持Metal撒踪,因此您將使用macOS
應(yīng)用程序过咬。
注意:本教程的挑戰(zhàn)項目的項目文件還包括
iOS target
。
使用Cocoa App
模板創(chuàng)建一個新的macOS
應(yīng)用程序制妄。
將項目命名為Pipeline
并選中Use Storyboards
掸绞。 不選中其余選項。
打開Main.storyboard
并在View Controller Scene
下選擇View
耕捞。
在Identity
檢查器中衔掸,將視圖從NSView
更改為MTKView
。
這會將主視圖設(shè)置為MetalKit View
俺抽。
打開ViewController.swift
敞映。 在文件的頂部,導(dǎo)入MetalKit
框架:
import MetalKit
然后磷斧,將此代碼添加到viewDidLoad()
:
guard let metalView = view as? MTKView else {
fatalError("metal view not set up in storyboard")
}
你現(xiàn)在有了選擇振愿。 您可以將MTKView
子類化并在sb中使用此視圖。 在這種情況下弛饭,每個幀都會調(diào)用子類的draw(_ :)
冕末,并將繪圖代碼放在該方法中。 但是侣颂,在本教程中栓霜,您將設(shè)置符合MTKViewDelegate
的Renderer
類,并將Renderer
設(shè)置為MTKView
的委托横蜒。 MTKView每幀調(diào)用一個委托方法胳蛮,這是你放置必要的繪圖代碼的地方销凑。
注意:如果您來自不同的API世界,您可能正在尋找游戲循環(huán)結(jié)構(gòu)仅炊。 您可以選擇擴展
CAMetalLayer
而不是創(chuàng)建MTKView
斗幼。 然后,您可以使用CADisplayLink
進行計時抚垄;但Apple推出的MetalKit
協(xié)議可以更輕松地管理游戲循環(huán)蜕窿。
The Renderer Class - Renderer類
創(chuàng)建一個名為Renderer.swift
的新Swift文件,并使用以下代碼替換其內(nèi)容:
import MetalKit
class Renderer: NSObject {
init(metalView: MTKView) {
super.init()
}
}
extension Renderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
print("draw")
}
}
在這里呆馁,您創(chuàng)建一個初始化程序桐经,并使用兩個MTKView
委托方法使Renderer
遵守MTKViewDelegate
:
-
mtkView(_:drawableSizeWillChange :)
:每次窗口大小改變時調(diào)用。 這允許您更新渲染坐標系浙滤。 -
draw(in :)
:每幀調(diào)用一次阴挣。
在ViewController.swift
中,添加一個屬性來保存renderer
:
var renderer: Renderer?
在viewDidLoad()
的末尾纺腊,初始化renderer
:
renderer = Renderer(metalView: metalView)
Initialization - 初始化
首先畔咧,您需要設(shè)置Metal
環(huán)境。
Metal
比OpenGL
具有一個主要優(yōu)勢揖膜,因為您可以預(yù)先實例化某些對象誓沸,而不是在每個幀中創(chuàng)建它們。 下圖顯示了您可以在應(yīng)用程序開頭創(chuàng)建的一些對象壹粟。
- `MTLDevice``:GPU硬件設(shè)備的軟件引用拜隧。
-
MTLCommandQueue
:負責(zé)每幀創(chuàng)建和組織MTLCommandBuffers
。 -
MTLLibrary
:包含頂點和片段著色器函數(shù)的源代碼趁仙。 -
MTLRenderPipelineState
:設(shè)置繪圖的信息虹蓄,例如要使用的著色器函數(shù),要使用的深度和顏色設(shè)置以及如何讀取頂點數(shù)據(jù)幸撕。 -
MTLBuffer
:以可以發(fā)送到GPU的形式保存數(shù)據(jù)薇组,例如頂點信息。
通常坐儿,您的應(yīng)用程序中將有一個MTLDevice
律胀,一個MTLCommandQueue
和一個MTLLibrary
對象。 您還將擁有幾個MTLRenderPipelineState
對象貌矿,這些對象將定義各種管道狀態(tài)炭菌,以及幾個用于保存數(shù)據(jù)的MTLBuffers
。
但是逛漫,在使用這些對象之前黑低,需要初始化它們。 將這些屬性添加到Renderer
:
static var device: MTLDevice!
static var commandQueue: MTLCommandQueue!
var mesh: MTKMesh!
var vertexBuffer: MTLBuffer!
var pipelineState: MTLRenderPipelineState!
這些是保持對不同對象的引用所需的屬性。 為方便起見克握,它們目前都是隱式解包的選項蕾管,但您可以在完成初始化后更改它。 此外菩暗,您不需要保留對MTLLibrary
的引用掰曾,因此無需創(chuàng)建它。
接下來停团,在super.init()
之前將此代碼添加到init(metalView :)
:
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("GPU not available")
}
metalView.device = device
Renderer.commandQueue = device.makeCommandQueue()!
這會初始化GPU并創(chuàng)建command queue
旷坦。 您正在使用設(shè)備和命令隊列的類屬性來確保每個屬性中只有一個存在。 在極少數(shù)情況下佑稠,您可能需要不止一個 - 但在大多數(shù)應(yīng)用程序中秒梅,一個就夠了。
最后舌胶,在super.init()
之后捆蜀,添加以下代碼:
metalView.clearColor = MTLClearColor(red: 1.0, green: 1.0,
blue: 0.8, alpha: 1.0)
metalView.delegate = self
這將metalView.clearColor
設(shè)置為奶油色。 它還將Renderer
設(shè)置為metalView
的代理辆琅,以便它調(diào)用MTKViewDelegate
繪圖方法。
Build并運行應(yīng)用程序以確保一切都已設(shè)置并正常運行这刷。 如果一切順利婉烟,你應(yīng)該看到一個普通的灰色窗口。 在調(diào)試控制臺中暇屋,您將反復(fù)看到“draw”
一詞似袁。 使用此選項可驗證您的應(yīng)用是否正在為每個幀調(diào)用draw(in :)
。
注意:您不會看到
metalView
的奶油色咐刨,因為您還沒有要求GPU進行任何繪圖昙衅。
Set Up the Data - 設(shè)置數(shù)據(jù)
構(gòu)建3D基元網(wǎng)格的類總是有用的。 在本教程中定鸟,您將設(shè)置一個用于創(chuàng)建3D形狀基元的類而涉,并且您將為其添加一個立方體。
創(chuàng)建一個名為Primitive.swift
的新Swift文件联予,并用以下代碼替換默認代碼:
import MetalKit
class Primitive {
class func makeCube(device: MTLDevice, size: Float) -> MDLMesh {
let allocator = MTKMeshBufferAllocator(device: device)
let mesh = MDLMesh(boxWithExtent: [size, size, size],
segments: [1, 1, 1],
inwardNormals: false, geometryType: .triangles,
allocator: allocator)
return mesh
}
}
此類方法返回一個立方體啼县。
在Renderer.swift
中,在init(metalView :)
中沸久,在調(diào)用super.init()
之前季眷,設(shè)置網(wǎng)格mesh
:
let mdlMesh = Primitive.makeCube(device: device, size: 1)
do {
mesh = try MTKMesh(mesh: mdlMesh, device: device)
} catch let error {
print(error.localizedDescription)
}
然后,設(shè)置包含您將發(fā)送到GPU的頂點數(shù)據(jù)的MTLBuffer
卷胯。
vertexBuffer = mesh.vertexBuffers[0].buffer
這將數(shù)據(jù)放入MTLBuffer
中子刮。 現(xiàn)在,您需要設(shè)置管道狀態(tài)窑睁,以便GPU知道如何呈現(xiàn)數(shù)據(jù)挺峡。
首先葵孤,設(shè)置MTLLibrary
并確保存在頂點和片段著色器函數(shù)。
繼續(xù)在super.init()
之前添加代碼:
let library = device.makeDefaultLibrary()
let vertexFunction = library?.makeFunction(name: "vertex_main")
let fragmentFunction = library?.makeFunction(name: "fragment_main")
您將在本教程后面創(chuàng)建這些著色器函數(shù)沙郭。 與OpenGL著色器不同佛呻,這些是在編譯項目時編譯的,這比動態(tài)編譯更有效病线。 結(jié)果存儲在庫中吓著。
現(xiàn)在,創(chuàng)建管道狀態(tài):
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mdlMesh.vertexDescriptor)
pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
do {
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
這為GPU設(shè)置了潛在的狀態(tài)送挑。 在開始管理頂點之前绑莺,GPU需要知道其完整狀態(tài)。 您可以設(shè)置GPU將調(diào)用的兩個著色器函數(shù)惕耕,還可以設(shè)置GPU將寫入的紋理的像素格式纺裁。
您還可以設(shè)置管道的頂點描述符(vertex descriptor)
。 這就是GPU將如何解釋您將在網(wǎng)格數(shù)據(jù)MTLBuffer
中呈現(xiàn)的頂點數(shù)據(jù)的方式司澎。
如果需要調(diào)用不同的頂點或片段函數(shù)欺缘,或使用不同的數(shù)據(jù)布局,那么您將需要更多的管道狀態(tài)挤安。 創(chuàng)建管道狀態(tài)相對耗時谚殊,這就是為什么你提前做到這一點的原因,但在幀期間切換管道狀態(tài)是快速和有效的蛤铜。
初始化完成嫩絮,您的項目將編譯。 但是围肥,如果您嘗試運行它剿干,則會出現(xiàn)錯誤,因為您尚未設(shè)置著色器函數(shù)穆刻。
Render Frames - 渲染幀
在Renderer.swift
中置尔,使用以下代碼替換draw(in :)
中的print
語句:
guard let descriptor = view.currentRenderPassDescriptor,
let commandBuffer = Renderer.commandQueue.makeCommandBuffer(),
let renderEncoder =
commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
return
}
// drawing code goes here
renderEncoder.endEncoding()
guard let drawable = view.currentDrawable else {
return
}
commandBuffer.present(drawable)
commandBuffer.commit()
這將設(shè)置渲染命令編碼器(render command encoder)
并將視圖的可繪制紋理呈現(xiàn)給GPU。
Drawing - 繪制
在CPU方面氢伟,要準備GPU撰洗,您需要為其提供數(shù)據(jù)和管道狀態(tài)。 然后腐芍,您需要發(fā)出繪制調(diào)用差导。
仍在draw(in:)
中,替換注釋:
// drawing code goes here
用
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
for submesh in mesh.submeshes {
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: submesh.indexBuffer.offset)
}
當(dāng)您在draw(in:)
結(jié)束猪勇,提交命令緩沖區(qū)時设褐,這向GPU指示數(shù)據(jù)和管道都已設(shè)置并且GPU可以接管。
后記
本篇主要講述了Metal渲染管道教程,感興趣的給個贊或者關(guān)注~~~