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文件