import UIKit
import Metal
import GLKit
struct Vertex {
let position: simd_float4
let color: simd_float4
}
class MetalView: UIView {
let device = MTLCreateSystemDefaultDevice()
var pipeLineState: MTLRenderPipelineState!
var commandQueue: MTLCommandQueue!
var vertexBuffer: MTLBuffer!
override init(frame: CGRect) {
super.init(frame: frame)
let layer = self.layer as! CAMetalLayer
layer.device = self.device
layer.pixelFormat = .bgra8Unorm
layer.drawableSize = CGSize.init(width: frame.width * UIScreen.main.scale , height: frame.height * UIScreen.main.scale)
self.config()
}
override class var layerClass: AnyClass{
return CAMetalLayer.classForCoder()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func config(){
let vertexData = [Vertex.init(position: simd_float4.init(-1, -0.5, 0.0, 1), color: simd_float4.init(1, 0, 0.0, 1.0)),Vertex.init(position: simd_float4.init(1, -0.5, 0.0, 1.0), color: simd_float4.init(0, 1, 0, 1)),Vertex.init(position: simd_float4.init(0.0, 0.5, 0.0, 1.0), color: simd_float4.init(0, 0, 1, 1))]
vertexBuffer = device!.makeBuffer(bytes: vertexData, length: vertexData.count * MemoryLayout<Vertex>.size, options: [])!
let library = device?.makeDefaultLibrary()
let renderPipeDescriptor = MTLRenderPipelineDescriptor.init()
renderPipeDescriptor.fragmentFunction = library?.makeFunction(name: "basic_fragment")
renderPipeDescriptor.vertexFunction = library?.makeFunction(name: "basic_vertex")
renderPipeDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
do{
self.pipeLineState = try device!.makeRenderPipelineState(descriptor: renderPipeDescriptor)
self.commandQueue = self.device!.makeCommandQueue()
let display = CADisplayLink.init(target: self, selector: #selector(render))
display.add(to: .current, forMode: .common)
}catch{
}
}
@objc func render(){
let renderpassdescriptor = MTLRenderPassDescriptor.init()
renderpassdescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1.0)
let layer = self.layer as! CAMetalLayer
let drawable = layer.nextDrawable()
renderpassdescriptor.colorAttachments[0].texture = drawable?.texture
renderpassdescriptor.colorAttachments[0].loadAction = .clear
let commandbuffer = self.commandQueue.makeCommandBuffer()
let renderencoder = commandbuffer?.makeRenderCommandEncoder(descriptor: renderpassdescriptor)
renderencoder?.setVertexBuffer(self.vertexBuffer, offset: 0, index: 0)
renderencoder?.setRenderPipelineState(self.pipeLineState)
renderencoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
renderencoder?.endEncoding()
commandbuffer?.present(drawable!)
commandbuffer?.commit()
}
}
着色器代碼
#include <metal_stdlib>
using namespace metal;
struct Vertex{
float4 position [[position]];
float4 color;
};
vertex Vertex basic_vertex(constant Vertex *vertices [[buffer(0)]],unsigned int vid[[vertex_id]]){
return vertices[vid];
}
fragment float4 basic_fragment(Vertex vert [[stage_in]]){
return vert.color;
}
這裏有個地方需要注意,就是CAMetalLayer的drawSize一定要設置成view的寬度乘以屏幕的倍數,即scale,不然繪製出的圖形會很不清晰且直線鋸齒現象嚴重,也可以試試乘以其他更大的數值,你會發現乘以一個更大的數值並不會讓鋸齒現象比乘以scale好,而且如果乘以的數值很大,比如20,圖形會重新出現嚴重的鋸齒。着色器代碼可以以純字符串的形式寫在MetalView文件中,但最好是寫在一個.metal文件裏,編譯時系統會自動編譯.metal文件