首先创建一个设备检测GPU是否支持。
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("GPU is not supported") }
通过设备创建MTKView
let view = MTKView(frame: frame, device: device)
通过Model I/O创建一个球模型,并设置数据用于更容易呈现的缓冲区。
let mdlMesh = MDLMesh(sphereWithExtent: [Float(200.0/frame.size.width),Float( 200.0/frame.size.height), 0.0],
segments: [100, 100],
inwardNormals: false,
geometryType: .triangles,
allocator: allocator)
从导入模型顶点到生成最终图像的整个过程,通常被称为呈现管道。呈现管道是一个列表命令发送给GPU,以及资源(顶点,材质和灯光)制作最后的图像。管道包括可编程和非可编程函数,被称为顶点函数和片元函数。
let descriptor = MTLRenderPipelineDescriptor()
descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
descriptor.vertexFunction = vertexFunction
descriptor.fragmentFunction = fragmentFunction
descriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
pipelineState = try! device.makeRenderPipelineState(descriptor: descriptor)
如果游戏只是呈现一个静止的图像,那么它就不会有多少乐趣。移动一个字符在屏幕周围的流动方式要求GPU渲染一个静态图像大约每秒60次。每个静止图像被称为一帧,图像出现的速度称为帧速率。当你最喜欢的游戏出现卡顿时,通常是因为帧率比较低,特别是当有过多的背景处理会消耗GPU。通过实现MTKViewDelegate方法进行渲染,从而完成图像的更新。
接下来看一下运行效果吧
所有代码晒一下,仅供参考
import UIKit
import MetalKit
class Deom1: UIView {
private var device:MTLDevice!
private var pipelineState:MTLRenderPipelineState!
private var mesh:MTKMesh!
override init(frame: CGRect) {
super.init(frame: frame)
device = MTLCreateSystemDefaultDevice()
let view = MTKView(frame: frame, device: device)
view.clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 1)
view.delegate = self
self.addSubview(view)
let allocator = MTKMeshBufferAllocator(device: device)
let mdlMesh = MDLMesh(sphereWithExtent: [Float(200.0/frame.size.width),Float( 200.0/frame.size.height), 0.0],
segments: [100, 100],
inwardNormals: false,
geometryType: .triangles,
allocator: allocator)
mesh = try! MTKMesh(mesh: mdlMesh, device: device)
let shader = """
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
float4 position [[ attribute(0) ]];
};
vertex float4 vertex_main(const VertexIn vertex_in [[ stage_in ]]) {
return vertex_in.position;
}
fragment float4 fragment_main() {
return float4(1, 0, 0, 1);
}
"""
let library = try! device.makeLibrary(source: shader, options: nil)
let vertexFunction = library.makeFunction(name: "vertex_main")
let fragmentFunction = library.makeFunction(name: "fragment_main")
let descriptor = MTLRenderPipelineDescriptor()
descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
descriptor.vertexFunction = vertexFunction
descriptor.fragmentFunction = fragmentFunction
descriptor.vertexDescriptor =
MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
pipelineState = try! device.makeRenderPipelineState(descriptor: descriptor)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension Deom1:MTKViewDelegate{
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let commandQueue = device.makeCommandQueue() else {
fatalError("Could not create a command queue") }
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let descriptor = view.currentRenderPassDescriptor,
let renderEncoder =
commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)
else { fatalError() }
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(mesh.vertexBuffers[0].buffer,
offset: 0, index: 0)
guard let submesh = mesh.submeshes.first else {
fatalError()
}
renderEncoder.drawIndexedPrimitives(type: .line,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: 0)
renderEncoder.endEncoding()
guard let drawable = view.currentDrawable else {
fatalError()
}
commandBuffer.present(drawable)
commandBuffer.commit()
}
}