首先創建一個設備檢測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方法進行渲染,從而完成圖像的更新。
接下來看一下運行效果吧
球.png
所有代碼曬一下,僅供參考
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()
}
}