Metal 练习:第一篇入门2D
Metal 练习:第一篇
在 iOS 8, Apple 发布了自己的3D图形GPU加速器:Metal。
Metal与OpenGL ES相似,都是一套底层的API来与3D图形硬件进行交互。不同的是Metal不是跨平台,从这一篇开始,我们将会介绍 Metal API。 将会学习Metal中一些重要的类,如device,command queue等等。
* Metal应用不能跑在 iOS的模拟器上,需要一台真机 A7芯片及以上
Metal vs. OpenGL ES
OpenGL ES被设计在跨平台上使用,那意味着你可以用C++来写OpenGL ES ,然后修改很少的代码就可以跑在其它的平台上,如安卓。 Apple认为OpenGL ES的跨平台支持做的非常好,但是还没有充分利用Apple的硬件的优势,所以Apple要采用一种全新的方式将软件与硬件结合更完美。 所以就有了Metal。可以提供10倍的绘制速度于OpenGL ES。参考 WWDC2014 keynote
编码
代码主要有两个文件 ViewController.swift 和 Shaders.metal, 将这两个文件拉到一个新建的工程可以直接运行。 编码的步骤及注释如下
// ViewController.swift
/// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// 步骤:
/// 在开始渲染前有几个必要的步骤创建Metal
/// 1. MTLDevice
/// 2. CAMetalLayer
/// 3. Vertex Buffer
/// 4. Vertex Shader
/// 5. Fragment Shader
/// 6. Render Pipeline
/// 7. Command Queue
/// 开始渲染
/// 1. Create a Display Link
/// 2. Create a Render Pass Descriptor
/// 3. Create a Command Buffer
/// 4. Create a Render Command Encoder
/// 5. Commit your Command Buffer
/// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import UIKit
import Metal
class ViewController: UIViewController {
/// direct connect to the GPU, other Metal Objects(like command queues, buffers and textures) you need using this MTLDevie
var device: MTLDevice!
/// a special subclass of CALayer for Metal
var metalLayer: CAMetalLayer!
var vertexBuffer: MTLBuffer!
var pipelineState: MTLRenderPipelineState!
/// 命令的有序列表,让GPU执行
var commandQueue: MTLCommandQueue!
/// -------------------------------------------------------------
var timer: CADisplayLink!
override func viewDidLoad() {
super.viewDidLoad()
// 1. create MTLDevice
device = MTLCreateSystemDefaultDevice()
// 2. create CAMetalLayer
metalLayer = CAMetalLayer()
// 2.1 给layer指定device
metalLayer.device = device
// 2.2 8 bytes 以这个顺序 blue, green, red, alpha
metalLayer.pixelFormat = .bgra8Unorm
// 2.3 推荐使用 true
metalLayer.framebufferOnly = true
// 2.4 设置图层的frame
metalLayer.frame = view.layer.frame
// 2.5 添加到图层上
view.layer.addSublayer(metalLayer)
// 3. 创建 vertex buffer
let vertexData: [Float] = [
0.0, 1.0, 0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
]
// 3.1 以 bytes 形式获得 vertexData 的大小, 首元素的大小 * count
let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0])
// 3.2 用下面的方法在GPU上创建一个新的buffer, 将 vertexData 数据从CPU绑定到buffer上
vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: [])
// 4. 创建 vertex shader, 见 Shaders.metal 文件
// 5. 创建 fragment shader,见 Shaders.metal 文件
// 6. 创建 render pipeline
// 6.1 包含在工程内的预编译 `shaders` 都可以通过 `MTLLibrary` 访问,然后根据名字查找
let defaultLibrary = device.makeDefaultLibrary()!
let fragmentProgram = defaultLibrary.makeFunction(name: "basic_fragment")
let vertexProgram = defaultLibrary.makeFunction(name: "basic_vertex")
// 6.2 设置 render pipeline 配置
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = vertexProgram
pipelineStateDescriptor.fragmentFunction = fragmentProgram
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
// 6.3 绑定到 pipeline state
pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
// 7. 创建 command queue
commandQueue = device.makeCommandQueue()!
/// -------------------------------------------------------------
// Render 1. 创建一个Display Link,一个定时器与屏幕刷新率同步
timer = CADisplayLink(target: self, selector: #selector(gameloop))
timer.add(to: RunLoop.main, forMode: .default)
// Render 2. 创建 MTLRenderPassDescriptor,配置texture渲染
// 见 render() 方法
// Render 3. 创建 command buffer
// 见 render() 方法
// Render 4. 创建 render command encoder
// 见 render() 方法
// Render 5. 提交 command buffer
// 见 render() 方法
}
}
extension ViewController {
func render() {
// Render 2.
guard let drawable = metalLayer?.nextDrawable() else { return }
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0,
green: 104.0 / 255.0,
blue: 55.0 / 255.0,
alpha: 1.0)
// Render 3. 想象成一个 render commmands 队列,直到提交时才会执行
let commandBuffer = commandQueue.makeCommandBuffer()!
// Render 4. 创建 render Encoder,指定了管线和vertex buffer
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
renderEncoder.endEncoding()
// Render 5.
commandBuffer.present(drawable)
commandBuffer.commit()
}
@objc func gameloop() {
autoreleasepool {
self.render()
}
}
}
// 创建方法: File -> New -> File -> Metal File
// Shaders.metal
#include
using namespace metal;
// 4. 创建 vertex shader
vertex float4 basic_vertex( // vertex shader 要以关键字 `vertex` 开头,必须有一个返回值(float4:包含4个float值的向量)
const device packed_float3* vertex_array[[buffer(0)]], // packed_float3:包含3个float向量,[[...]]语义定义一个属性,[[buffer(0)]]表明从Metal代码中传递来的数据第一个缓冲区将填充这个参数
unsigned int vid [[vertex_id]]) { // 特定的`vertex_id`属性,将用顶点数组中指定索引位置填充
return float4(vertex_array[vid], 1.0); // 根据`vid`找到顶点数据且转换为float4并返回
}
// 5. 创建 fragment shader
fragment half4 basic_fragment() { // fragment shader 要以关键字 `fragment` 开头,要返回fragment的color, half4:RGBA四分量的颜色值
return half4(1.0); // 此处返回的全1,是白色
}
参考及更多资料
- 原文:Metal Tutorial: Getting Started
- Apple’s Metal for Developers page
- Apple’s Metal Programming Guide
- Apple’s Metal Shading Language Guide
- WWDC2014 For Metal