Metal框架详细解析(四十八) —— 将项目从OpenGL转化到Metal(一)

版本记录

版本号 时间
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

MTKViewMetalKit框架的一部分。 要访问此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,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容