版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2019.01.18 星期五 |
前言
很多做視頻和圖像的,相信對這個框架都不是很陌生,它渲染高級3D圖形,并使用GPU執行數據并行計算。接下來的幾篇我們就詳細的解析這個框架。感興趣的看下面幾篇文章。
1. Metal框架詳細解析(一)—— 基本概覽
2. Metal框架詳細解析(二) —— 器件和命令(一)
3. Metal框架詳細解析(三) —— 渲染簡單的2D三角形(一)
4. Metal框架詳細解析(四) —— 關于GPU Family 4(一)
5. Metal框架詳細解析(五) —— 關于GPU Family 4之關于Imageblocks(二)
6. Metal框架詳細解析(六) —— 關于GPU Family 4之關于Tile Shading(三)
7. Metal框架詳細解析(七) —— 關于GPU Family 4之關于光柵順序組(四)
8. Metal框架詳細解析(八) —— 關于GPU Family 4之關于增強的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細解析(九) —— 關于GPU Family 4之關于線程組共享(六)
10. Metal框架詳細解析(十) —— 基本組件(一)
11. Metal框架詳細解析(十一) —— 基本組件之器件選擇 - 圖形渲染的器件選擇(二)
12. Metal框架詳細解析(十二) —— 基本組件之器件選擇 - 計算處理的設備選擇(三)
13. Metal框架詳細解析(十三) —— 計算處理(一)
14. Metal框架詳細解析(十四) —— 計算處理之你好,計算(二)
15. Metal框架詳細解析(十五) —— 計算處理之關于線程和線程組(三)
16. Metal框架詳細解析(十六) —— 計算處理之計算線程組和網格大小(四)
17. Metal框架詳細解析(十七) —— 工具、分析和調試(一)
18. Metal框架詳細解析(十八) —— 工具、分析和調試之Metal GPU Capture(二)
19. Metal框架詳細解析(十九) —— 工具、分析和調試之GPU活動監視器(三)
20. Metal框架詳細解析(二十) —— 工具、分析和調試之關于Metal著色語言文件名擴展名、使用Metal的命令行工具構建庫和標記Metal對象和命令(四)
21. Metal框架詳細解析(二十一) —— 基本課程之基本緩沖區(一)
22. Metal框架詳細解析(二十二) —— 基本課程之基本紋理(二)
23. Metal框架詳細解析(二十三) —— 基本課程之CPU和GPU同步(三)
24. Metal框架詳細解析(二十四) —— 基本課程之參數緩沖 - 基本參數緩沖(四)
25. Metal框架詳細解析(二十五) —— 基本課程之參數緩沖 - 帶有數組和資源堆的參數緩沖區(五)
26. Metal框架詳細解析(二十六) —— 基本課程之參數緩沖 - 具有GPU編碼的參數緩沖區(六)
27. Metal框架詳細解析(二十七) —— 高級技術之圖層選擇的反射(一)
28. Metal框架詳細解析(二十八) —— 高級技術之使用專用函數的LOD(一)
29. Metal框架詳細解析(二十九) —— 高級技術之具有參數緩沖區的動態地形(一)
30. Metal框架詳細解析(三十) —— 延遲照明(一)
31. Metal框架詳細解析(三十一) —— 在視圖中混合Metal和OpenGL渲染(一)
32. Metal框架詳細解析(三十二) —— Metal渲染管道教程(一)
33. Metal框架詳細解析(三十三) —— Metal渲染管道教程(二)
34. Metal框架詳細解析(三十四) —— Hello Metal! 一個簡單的三角形的實現(一)
35. Metal框架詳細解析(三十五) —— Hello Metal! 一個簡單的三角形的實現(二)
36. Metal框架詳細解析(三十六) —— Metal編程指南之概覽(一)
37. Metal框架詳細解析(三十七) —— Metal編程指南之基本Metal概念(二)
38. Metal框架詳細解析(三十八) —— Metal編程指南之命令組織和執行模型(三)
39. Metal框架詳細解析(三十九) —— Metal編程指南之資源對象:緩沖區和紋理(四)
40. Metal框架詳細解析(四十) —— Metal編程指南之函數和庫(五)
41. Metal框架詳細解析(四十一) —— Metal編程指南之圖形渲染:渲染命令編碼器之Part 1(六)
42. Metal框架詳細解析(四十二) —— Metal編程指南之圖形渲染:渲染命令編碼器之Part 2(七)
43. Metal框架詳細解析(四十三) —— Metal編程指南之數據并行計算處理:計算命令編碼器(八)
44. Metal框架詳細解析(四十四) —— Metal編程指南之緩沖和紋理操作:Blit命令編碼器(九)
45. Metal框架詳細解析(四十五) —— Metal編程指南之Metal工具(十)
46. Metal框架詳細解析(四十六) —— Metal編程指南之Tessellation(十一)
47. Metal框架詳細解析(四十七) —— Metal編程指南之資源堆(十二)
開始
首先看下寫作環境
Swift 4.2, iOS 12, Xcode 10
在這篇Metal教程中,您將學習如何將項目從OpenGL移動到Apple的3D圖形API:Metal。
Metal于2014年推出,作為基于GPU計算的通用API。在2018年,蘋果公司在iOS 12中的iOS和MacOS中都棄用了的OpenGL。
在本教程中,您將學習如何將應用程序從使用OpenGL轉換為Metal。要完成本教程,您需要一個可用的OpenGL應用程序。
在開始之前,您可能想要查看Metal和OpenGL上的這些優秀資源。
如果您沒有3D圖形經驗,請不要擔心!你仍然可以看懂。如果您對3D編程或OpenGL有一些經驗,您可能會發現本教程很簡單。許多相同的概念適用于Metal。
注意:Metal應用程序無法在iOS模擬器上運行。它們需要帶有Apple A7 device或更高版本的設備。要完成本教程,您需要A7及其以上的設備。
OpenGL ES vs. Metal
OpenGL ES
旨在成為一個跨平臺的框架。 這意味著,通過一些小的修改,您可以在其他平臺上運行C ++ OpenGL ES
代碼,例如Android
。
OpenGL ES
的跨平臺支持很不錯,但Apple意識到它缺少所有優質Apple產品所具有的操作系統,硬件和軟件的優點集成。 因此,它專門為Apple硬件設計了圖形API。 我們的目標是在支持最新和最強大功能的同時實現低開銷和高性能。
答案就是Metal
,與OpenGL ES
相比,它可以為您的應用提供高達10?
的繪制調用次數。 效果令人驚嘆 - 您可能會在WWDC 2014 keynote主題演講中的Zen Garden
示例中記住它們。
1. Understanding Conceptual Differences
從開發的角度來看,OpenGL和Metal是相似的。在這兩種情況下,您都可以使用數據設置緩沖區以傳遞給GPU并指定頂點和片段著色器。在OpenGL
項目中,有一個GLKBaseEffect
,它是著色器之上的抽象。 Metal沒有這樣的API,所以你需要自己編寫著色器。但不要擔心 - 它不是太復雜!
OpenGL和Metal之間的最大區別在于,在Metal中,您通常使用兩種類型的對象:
- 描述符對象
(Descriptor objects)
。 - 編譯狀態對象
(Compiled-state objects)
。
這個想法很簡單。您創建一個描述符對象并進行編譯。編譯狀態對象是GPU優化的資源。創建和編譯都是昂貴的操作,因此我們的想法是盡可能少地執行它們,然后再使用編譯狀態對象進行操作。
這種方法意味著在使用Metal時,您不需要在渲染循環上進行大量的設置操作。這使得它比OpenGL更有效,由于體系結構的限制,OpenGL無法做到這一點。
是時候自己探索這種差異吧!
Integrating Metal
打開已有的項目OpenGLKit.xcodeproj
,你會看到一個實用OpenGL的項目,構建并運行如下所示:
你應該看到一個彩色方形旋轉。 此方塊使用OpenGL渲染。 但是,由于此項目的部署目標是iOS 12,因此有幾個OpenGL棄用警告。 您可以在Issue navigator
中的Xcode中查看這些內容。
現在你要用Metal畫同一個方格。 并擺脫所有那些討厭的警告!
打開ViewController.swift
,將ViewController
更改為UIViewController
的子類,而不是GLKViewController
。 在Metal中,沒有MetalViewController
這樣的東西。 相反,你必須在UIViewController
中使用MTKView
。
MTKView
是MetalKit
框架的一部分。 要訪問此API,
在文件頂部添加以下內容:
import MetalKit
1. Switching From OpenGL
現在是時候做一些OpenGL
清理了。 跟著這些步驟:
- 1) 將
setupGL()
的兩次出現重命名為setupMetal()
。 - 2) 刪除
tearDownGL()
和deinit()
方法。 使用Metal
,不需要像這樣明確清理。 - 3) 查找并刪除整個擴展
GLKViewControllerDelegate
,因為此視圖控制器不再是GLKViewController
。 請注意,glkViewControllerUpdate
包含用于旋轉的邏輯。 這很有用,但是暫時刪除它。 - 4) 從
setupMetal()
的頂部刪除以下代碼:
context = EAGLContext(api: .openGLES3)
EAGLContext.setCurrent(context)
if let view = self.view as? GLKView, let context = context {
view.context = context
delegate = self
}
- 5) 從
ViewController
頂部刪除以下屬性:
private var context: EAGLContext?
private var effect = GLKBaseEffect()
private var ebo = GLuint()
private var vbo = GLuint()
private var vao = GLuint()
最后,在ViewController
類聲明的頂部,向MTKView
添加一個IBOutlet
:
@IBOutlet weak var metalView: MTKView!
2. Setting Up the Storyboard
ViewController
不再是GLKViewController
,因此您需要在故事板中進行一些更改。
打開Main.storyboard
。 在此示例中,故事板包含兩個場景,均命名為View Controller Scene
。 一個有GLKView
,另一個包含MTKView
和你剛剛添加到源代碼的outlet
的連接。
您需要做的就是使用MTKView
作為初始視圖控制器來設置場景。 找到當前沒有箭頭指向它的場景。 單擊頂部的欄以選擇視圖控制器。 或者,您可以在文檔大綱窗格中選擇它。 然后打開屬性檢查器并選中Is Initial View Controller
。
完成后,您可以刪除第一個場景。 干得好!
Setting Up Metal
你準備好了嗎? 是時候使用一些Metal了!
在Metal中,您將用于訪問GPU的主要對象是MTLDevice
。 下一個最重要的對象是MTLCommandQueue
。 此對象是您將傳遞編碼幀的隊列。
打開ViewController.swift
并添加以下屬性:
private var metalDevice: MTLDevice!
private var metalCommandQueue: MTLCommandQueue!
現在,轉到setupMetal()
。 用以下內容替換它的內容:
metalDevice = MTLCreateSystemDefaultDevice()
metalCommandQueue = metalDevice.makeCommandQueue()
metalView.device = metalDevice
metalView.delegate = self
這比以前更短了!
這將抓取系統默認的Metal
設備,然后從設備創建命令隊列。 然后它將設備分配給Metal視圖。 最后,它將視圖控制器設置為視圖的委托,以便在繪制和調整大小時接收回調。
現在您需要實現MTKViewDelegate
協議。
在ViewController.swift
的底部,添加以下擴展:
extension ViewController: MTKViewDelegate {
// 1
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
// 2
func draw(in view: MTKView) {
}
}
此擴展實現了兩種方法。
- 1) 當可繪制尺寸改變時,例如當屏幕旋轉時,調用此方法。
- 2) 調用此方法以執行實際繪圖。
1. Basic Drawing
你們都準備好了! 為了簡單起見,您將首先繪制灰色背景。
對于要在Metal中繪制的每個幀,必須創建一個命令緩沖區,您可以在其中指定要繪制的內容和方式。 然后,此緩沖區在CPU上編碼,并通過命令隊列發送到GPU。
在draw(in :)
中添加以下代碼:
// 1
guard let drawable = view.currentDrawable else {
return
}
let renderPassDescriptor = MTLRenderPassDescriptor() // 2
renderPassDescriptor.colorAttachments[0].texture = drawable.texture // 3
renderPassDescriptor.colorAttachments[0].loadAction = .clear // 4
renderPassDescriptor.colorAttachments[0]
.clearColor = MTLClearColor(red: 0.85, green: 0.85, blue: 0.85, alpha: 1.0) // 5
// 6
guard let commandBuffer = metalCommandQueue.makeCommandBuffer() else {
return
}
// 7
guard let renderEncoder = commandBuffer
.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
return
}
// Frame drawing goes here
renderEncoder.endEncoding() // 8
commandBuffer.present(drawable) // 9
commandBuffer.commit() // 10
這里面的東西有點多,這是上面代碼中發生的事情:
- 1) 確保有一個有效的
drawable
用于當前幀。 - 2)
MTLRenderPassDescriptor
包含一組附件,這些附件是渲染過程生成的像素的渲染目標。 - 3) 將視圖中的紋理設置為繪圖目標。
- 4) 清除渲染開始時的每個像素。
- 5) 指定清除顏色附件時要使用的顏色。在這種情況下,它是一個可愛的85%灰色。
- 6) 請求命令隊列創建新的命令緩沖區。
- 7) 創建一個編碼器對象,可以將圖形渲染命令編碼到命令緩沖區中。稍后您將在此聲明后添加實際繪圖代碼。
- 8) 聲明此編碼器生成的所有命令都已完成。
- 9) 盡快注冊可繪制的
drawable presentation
。 - 10) 提交此命令緩沖區以在命令隊列中執行。
總之,您可以創建命令緩沖區和命令編碼器。然后,在命令編碼器中進行繪制,并通過命令隊列將命令緩沖區提交給GPU。然后每次繪制幀時重復這些步驟。
注意:如果您正在使用模擬器,此時您將收到構建錯誤。您需要一個兼容的設備來構建和運行該應用程序。
構建并運行應用程序。
一個灰色面板完成了。
Drawing Primitives
繪制更有意義的東西需要一些時間。 所以,下面我們就開始!
要繪制內容,必須將表示對象的數據傳遞給GPU。 您已經有頂點結構來表示頂點數據,但是您需要進行一些小的更改才能將它與Metal一起使用。
打開ViewController.swift
并從中更改Indices
屬性:
var Indices: [GLubyte]
改成下面這種:
var Indices: [UInt32]
你將會很快看到為什么在繪制基元(primitives)
的時候需要這么更改。
1. Data Buffers
要將頂點數據傳遞給GPU,您需要創建兩個緩沖區:一個用于頂點,一個用于索引(indices)
。 這類似于OpenGL
的元素緩沖對象(EBO)
和頂點緩沖對象(VBO)
。
將這些屬性添加到ViewController
:
private var vertexBuffer: MTLBuffer!
private var indicesBuffer: MTLBuffer!
現在,在setupMetal()
中,在底部添加以下內容:
let vertexBufferSize = Vertices.size()
vertexBuffer = metalDevice
.makeBuffer(bytes: &Vertices, length: vertexBufferSize, options: .storageModeShared)
let indicesBufferSize = Indices.size()
indicesBuffer = metalDevice
.makeBuffer(bytes: &Indices, length: indicesBufferSize, options: .storageModeShared)
這要求metalDevice
創建用數據初始化的頂點和索引緩沖區。
現在,去draw(in:)
中,然后在之前:
renderEncoder.endEncoding()
添加下面代碼
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
renderEncoder.drawIndexedPrimitives(
type: .triangle,
indexCount: Indices.count,
indexType: .uint32,
indexBuffer: indicesBuffer,
indexBufferOffset: 0)
首先,它將頂點緩沖區傳遞給GPU,將其設置為索引0
。然后,它使用indicesBuffer
繪制三角形。 請注意,您需要指定索引類型uint32
。 這就是你之前更改索引類型的原因。
構建并運行應用程序。
崩潰! 這不好。 您將數據從CPU傳遞到GPU。 它崩潰是因為您沒有指定GPU應該如何使用這些數據。 你需要添加一些著色器! 幸運的是,這是下一步。
后記
本篇主要講述了將項目從OpenGL轉化到Metal,感興趣的給個贊或者關注~~~