图像

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

2019-01-18  本文已影响209人  刀客传奇

版本记录

版本号 时间
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中,您通常使用两种类型的对象:

这个想法很简单。您创建一个描述符对象并进行编译。编译状态对象是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清理了。 跟着这些步骤:

context = EAGLContext(api: .openGLES3)
EAGLContext.setCurrent(context)
    
if let view = self.view as? GLKView, let context = context {
  view.context = context
  delegate = self
}
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. 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

这里面的东西有点多,这是上面代码中发生的事情:

总之,您可以创建命令缓冲区和命令编码器。然后,在命令编码器中进行绘制,并通过命令队列将命令缓冲区提交给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,感兴趣的给个赞或者关注~~~

上一篇下一篇

猜你喜欢

热点阅读