swift使用metal加载三角形、平面图片、立体图像
Metal 是一个和 OpenGL ES 类似的面向底层的图形编程接口,通过使用相关的 api 可以直接操作 GPU ,最早在 2014 年的 WWDC 的时候发布,并于今年发布了 Metal 2。
Metal 是 iOS 平台独有的,意味着它不能像 OpenGL ES 那样支持跨平台,但是它能最大的挖掘苹果移动设备的 GPU 能力,进行复杂的运算,像 Unity 等游戏引擎都通过 Metal 对 3D 能力进行了优化, App Store 还有相应的运用 Metal 技术的游戏专题。
Metal 具有特点
GPU 支持的 3D 渲染
和 CPU 并行处理数据 (深度学习)
提供低功耗接口
可以和 CPU 共享资源内存
网上好多介绍Metal加载图像的文章都是用的OC语言,本篇用swift,以供参考。
Metal基本流程:
- 配置 Device 和 Queue
- 获取 CommandBuffer
- 配置 CommandBufferEncoder
- 配置 PipelineState
- 创建资源
- Encoder Buffer 【如有需要的话可以用 Threadgroups 来分组 Encoder 数据】
-
提交到 Queue 中
一。Metal简单加载一个三角形
//创建顶点结构体 struct VertexColor { var vex:vector_float2 var color:vector_float4 } class DzqRender: NSObject { var view : MTKView var commandQueue : MTLCommandQueue var device :MTLDevice var pipelineState:MTLRenderPipelineState? var viewSize :CGSize = CGSize.zero init(view:MTKView) { self.view = view //1.拿到 GPU 对象,Metal 中提供了 MTLDevice 的接口,代表了 GPU。 self.device = MTLCreateSystemDefaultDevice()! self.commandQueue = device.makeCommandQueue()! super.init() view.preferredFramesPerSecond = 60 viewSize = view.drawableSize var library = device.makeDefaultLibrary() // if let url = Bundle.main.url(forResource: "", withExtension: ""){ library = try? device.makeLibrary(URL: url) } let vfunc:MTLFunction? = library?.makeFunction(name: "vertexShader") let fFunc:MTLFunction? = library?.makeFunction(name: "fragmentShader") let description = MTLRenderPipelineDescriptor() description.vertexFunction = vfunc description.fragmentFunction = fFunc description.colorAttachments[0].pixelFormat = view.colorPixelFormat do { pipelineState = try device.makeRenderPipelineState(descriptor: description) } catch let error { print(error.localizedDescription) } } //1. 增加颜色/减小颜色的 标记 var growing = true; //2.颜色通道值(0~3) var primaryChannel = 0; //3.颜色通道数组colorChannels(颜色值) var colorChannels = [1.0, 0.0, 0.0, 1.0] //设置颜色 //4.颜色调整步长 let DynamicColorRate = 0.015; func makeFancyColor() -> Color { //5.判断 if(growing) { //动态信道索引 (1,2,3,0)通道间切换 let dynamicChannelIndex = (primaryChannel+1)%3; //修改对应通道的颜色值 调整0.015 colorChannels[dynamicChannelIndex] += DynamicColorRate; //当颜色通道对应的颜色值 = 1.0 if(colorChannels[dynamicChannelIndex] >= 1.0) { //设置为NO growing = false; //将颜色通道修改为动态颜色通道 primaryChannel = dynamicChannelIndex; } } else { //获取动态颜色通道 let dynamicChannelIndex = (primaryChannel+2)%3; //将当前颜色的值 减去0.015 colorChannels[dynamicChannelIndex] -= DynamicColorRate; //当颜色值小于等于0.0 if(colorChannels[dynamicChannelIndex] <= 0.0) { //又调整为颜色增加 growing = true; } } //创建颜色 var color = Color() //修改颜色的RGBA的值 color.red = colorChannels[0] color.green = colorChannels[1] color.blue = colorChannels[2] color.alpha = colorChannels[3] //返回颜色 return color; } } extension DzqRender : MTKViewDelegate{ // 当MTKView视图发生大小改变时调用 func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { viewSize = size } //每当视图需要渲染时调用 func draw(in view: MTKView) { // let color = makeFancyColor() // view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha) //为当前渲染的每个渲染传递创建一个新的命令缓冲区 let commandBuffer = commandQueue.makeCommandBuffer() commandBuffer?.label = "buffer" //5.判断renderPassDescriptor 渲染描述符是否创建成功,否则则跳过任何渲染. if let passDescriptor = view.currentRenderPassDescriptor, let state = pipelineState { let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: passDescriptor) let viewpoint:MTLViewport = MTLViewport(originX: 0, originY: 0, Double(viewSize.width), height: Double(viewSize.height), znear: -1, zfar: 1) renderEncoder?.setViewport(viewpoint) renderEncoder?.setRenderPipelineState(state) /* 在 Metal 中是归一化的坐标系,以屏幕中心为原点(0, 0, 0),且是始终不变的。面对屏幕,你的右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。长度单位这样来定:窗口范围按此单位恰好是(-1,-1)到(1,1),即屏幕左下角坐标为(-1,-1),右上角坐标为(1,1) */ // let vertex:[Float] = [ // -1.0, 0.0, 1, 0, 0, 1, // 2.0, 1.0, 0, 1, 0, 1, // 1.0, 0.5, 0, 0, 1, 1 // ] let vertex :[VertexColor] = [ VertexColor(vex: vector_float2(-1, 0), color: vector_float4(1, 0, 0, 1)), VertexColor(vex: vector_float2(1, 0), color: vector_float4(0, 1, 0, 1)), VertexColor(vex: vector_float2(0, 0.5), color: vector_float4(0, 0, 1, 1)), ] renderEncoder?.setVertexBytes(vertex, length: MemoryLayout<VertexColor>.size * 3, index: 0) //renderEncoder?.setVertexBytes(&Vpoint, length: MemoryLayout<Float>.size * 2, index: 1) renderEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3) //7.我们可以使用MTLRenderCommandEncoder 来绘制对象,但是这个demo我们仅仅创建编码器就可以了,我们并没有让Metal去执行我们绘制的东西,这个时候表示我们的任务已经完成. //即可结束MTLRenderCommandEncoder 工作 renderEncoder?.endEncoding() /* 当编码器结束之后,命令缓存区就会接受到2个命令. 1) present 2) commit 因为GPU是不会直接绘制到屏幕上,因此你不给出去指令.是不会有任何内容渲染到屏幕上. */ //8.添加一个最后的命令来显示清除的可绘制的屏幕 commandBuffer?.present(view.currentDrawable!) } //9.在这里完成渲染并将命令缓冲区提交给GPU commandBuffer?.commit() } }
着色器代码:
资源有了,我们要告诉 GPU 怎么去使用这些数据,这里就需要 Shader 了,这部分代码是在 GPU 中执行的,所以要用特殊的语言去编写,即 Metal Shading Language,它是 C++ 14的超集,封装了一些 Metal 的数据格式和常用方
#include <metal_stdlib>
//#include "DQRenderType.swift" using namespace metal; typedef struct { float2 position; float4 color; } VertexIn; typedef struct { float4 clipSpacePosition [[position]]; float4 color; } RasterizerData; vertex RasterizerData vertexShader(uint vertexid [[vertex_id]],constant VertexIn *vertexColor [[buffer(0)]]){ RasterizerData out; out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0); out.clipSpacePosition.xy = vertexColor[vertexid].position; // out.clipSpacePosition.y = vertexColor[vertexid + 1]; out.color = vertexColor[vertexid].color; // out.color.r = vertexColor[vertexid + 2]; // out.color.g = vertexColor[vertexid + 3]; // out.color.b = vertexColor[vertexid + 4]; // out.color.a = vertexColor[vertexid + 5]; return out; } fragment float4 fragmentShader(RasterizerData in [[stage_in]]){ return in.color; }
二、Metal加载平面图像
class ImageRender: NSObject { var view : MTKView var commandQueue : MTLCommandQueue? var device :MTLDevice var pipelineState:MTLRenderPipelineState? var viewSize :CGSize = CGSize.zero var vertexBuffer :MTLBuffer? var vertexIndex:MTLBuffer? var texture:MTLTexture? var loadtga:Bool = false init(view:MTKView,loadTga:Bool = false) { self.view = view self.device = view.device! self.commandQueue = device.makeCommandQueue()! super.init() view.preferredFramesPerSecond = 60 viewSize = view.drawableSize setUpPipeLineState() setUpVertex() setUpImageTexture(loadTga:loadTga) } func setUpPipeLineState() { var library = try? device.makeDefaultLibrary() if let url = Bundle.main.url(forResource: "TextureRender", withExtension: "metal"){ library = try? device.makeLibrary(URL: url) } let vfunc:MTLFunction? = library?.makeFunction(name: "vertexShader1") let fFunc:MTLFunction? = library?.makeFunction(name: "fragmentShader1") let description = MTLRenderPipelineDescriptor() description.vertexFunction = vfunc description.fragmentFunction = fFunc description.colorAttachments[0].pixelFormat = view.colorPixelFormat do { pipelineState = try device.makeRenderPipelineState(descriptor: description) } catch let error { print(error.localizedDescription) } commandQueue = device.makeCommandQueue() } func setUpVertex() { let vertexTex:[VertexTexture] = [ VertexTexture(vex: vector_float2(1, -1), tex: vector_float2(1, 0)), VertexTexture(vex: vector_float2(-1, -1), tex: vector_float2(0, 0)), VertexTexture(vex: vector_float2(-1, 1), tex: vector_float2(0, 1)), // VertexTexture(vex: vector_float2(1, -1), tex: vector_float2(1, 0)), // VertexTexture(vex: vector_float2(-1, 1), tex: vector_float2(0, 1)), VertexTexture(vex: vector_float2(1, 1), tex: vector_float2(1, 1)), ] vertexBuffer = device.makeBuffer(bytes: vertexTex, length: MemoryLayout<VertexTexture>.size * vertexTex.count, options: MTLResourceOptions.storageModeShared) func scaleShowImage(){ var vertexs:[Float] = [ 1,-1, 1,0, -1,-1, 0,0, -1,1, 0,1, 1,1, 1,1 ] var imageScale:(CGFloat,CGFloat) = (1,1) if let image = UIImage(named: imageName)?.cgImage { let width = image.width let height = image.height let scaleF = CGFloat(view.frame.height)/CGFloat(view.frame.width) let scaleI = CGFloat(height)/CGFloat(width) imageScale = scaleF>scaleI ? (1,scaleI/scaleF) : (scaleI/scaleF,1) } for (i,v) in vertexs.enumerated(){ if i % 4 == 0 { vertexs[i] = v * Float(imageScale.0) } if i % 4 == 1{ vertexs[i] = v * Float(imageScale.1) } } vertexBuffer = device.makeBuffer(bytes: vertexs, length: MemoryLayout<Float>.size * vertexs.count, options: MTLResourceOptions.storageModeShared) } //按图片比例显示 scaleShowImage() //索引绘图 let index:[Int32] = [ 0,1,2, 0,2,3 ] vertexIndex = device.makeBuffer(bytes: index, length: MemoryLayout<Int32>.size * 6, options: .storageModeShared) } func setUpImageTexture(loadTga:Bool = false) { var imageSoruce = UIImage(named: imageName) if loadTga { let url = Bundle.main.url(forResource: "Image", withExtension: "tga") imageSoruce = self.tgaTOImage(url: url!) } guard let image = imageSoruce?.cgImage else { return } let width = image.width let height = image.height //开辟内存,绘制到这个内存上去 let spriteData: UnsafeMutablePointer = UnsafeMutablePointer<GLubyte>.allocate(capacity: MemoryLayout<GLubyte>.size * width * height * 4) UIGraphicsBeginImageContext(CGSize( width, height: height)) //获取context let spriteContext = CGContext(data: spriteData, width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: image.colorSpace!, bitmapInfo: image.bitmapInfo.rawValue) spriteContext?.translateBy(x:0 , y: CGFloat(height)) spriteContext?.scaleBy(x: 1, y: -1) spriteContext?.draw(image, in: CGRect(x: 0, y: 0, width, height: height)) UIGraphicsEndImageContext() // spriteData let textureDescriptor = MTLTextureDescriptor() textureDescriptor.pixelFormat = .rgba8Unorm //MTLPixelFormatRGBA8Unorm defoat textureDescriptor.width = image.width textureDescriptor.height = image.height texture = device.makeTexture(descriptor: textureDescriptor) texture?.replace(region: MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize( image.width, height: image.height, depth: 1)), mipmapLevel: 0, withBytes: spriteData, bytesPerRow: 4 * image.width) free(spriteData) } func tgaTOImage(url:URL) -> UIImage? { if url.pathExtension.caseInsensitiveCompare("tga") != .orderedSame { return nil } guard let fileData = try? Data.init(contentsOf: url) else { print("打开tga文件失败!") return nil } let image = UIImage(data: fileData) return image } } extension ImageRender:MTKViewDelegate{ func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let queue = commandQueue, let buffer = queue.makeCommandBuffer(), let renderPassDiscriptor = view.currentRenderPassDescriptor, let encoder = buffer.makeRenderCommandEncoder(descriptor: renderPassDiscriptor), let pipeState = pipelineState else { return } encoder.label = "renderEncoder" encoder.setRenderPipelineState(pipeState) encoder.setViewport(MTLViewport(originX: 0, originY: 0, Double(viewSize.width), height: Double(viewSize.height), znear: -1, zfar: 1)) encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) encoder.setFragmentTexture(texture, index: 0) // encoder.drawPrimitives(type: MTLPrimitiveType.triangleStrip, vertexStart: 0, vertexCount: 6)//不实用索引绘图绘制 encoder.drawIndexedPrimitives(type: .triangleStrip, indexCount: 6, indexType: .uint32, indexBuffer: vertexIndex!, indexBufferOffset: 0)//使用索引绘图绘制 encoder.endEncoding() buffer.present(view.currentDrawable!) buffer.commit() } }
shader代码:
#include <metal_stdlib> //#include "DQRenderType.swift" using namespace metal; typedef struct { float2 position; float2 color; } VertexIn; typedef struct { float4 clipSpacePosition [[position]]; float2 color; } RasterizerData; vertex RasterizerData vertexShader1(uint vertexid [[vertex_id]],constant VertexIn *vertexColor [[buffer(0)]]){ RasterizerData out; out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0); out.clipSpacePosition.xy = vertexColor[vertexid].position; out.color = vertexColor[vertexid].color; return out; } fragment float4 fragmentShader1(RasterizerData in [[stage_in]],texture2d<float> texture [[texture(0)]]){ constexpr sampler textureSampler234(mag_filter::linear,min_filter::linear); float4 color = texture.sample(textureSampler234, in.color); return color; }
三、Metal加载立体图像
class CubeRender: NSObject { var view : MTKView var commandQueue : MTLCommandQueue? var device :MTLDevice var pipelineState:MTLRenderPipelineState? var viewSize :CGSize = CGSize.zero var vertexBuffer :MTLBuffer? var vertexIndex:MTLBuffer? var texture:MTLTexture? var indexCount :Int = 0 var button : UIButton! var switchX,switchY,switchZ :UISwitch init(view:MTKView) { self.view = view self.device = view.device! self.commandQueue = device.makeCommandQueue()! switchX = UISwitch(frame: CGRect(x: 20 , y: view.frame.size.height - 100, 100, height: 60)) switchY = UISwitch(frame: CGRect(x: 10 , y: view.frame.size.height - 100, 100, height: 60)) switchY.center.x = view.center.x switchZ = UISwitch(frame: CGRect(x: view.frame.size.width - 110 , y: view.frame.size.height - 100, 100, height: 60)) view.addSubview(switchX) view.addSubview(switchY) view.addSubview(switchZ) super.init() view.preferredFramesPerSecond = 60 viewSize = view.drawableSize switchX.backgroundColor = .gray switchY.backgroundColor = .gray switchZ.backgroundColor = .gray button = UIButton(frame: CGRect(x: 0, y: view.frame.size.height - 160, 100, height: 50)) button.setTitle("旋转", for: UIControl.State.normal) button.center.x = view.center.x button.backgroundColor = .gray button.addTarget(self, action: #selector(rotate(btn:)), for: .touchUpInside) view.addSubview(button) setUpPipeLineState() setUpVertex() setUpImageTexture() } @objc func rotate(btn:UIButton){ btn.isSelected = !btn.isSelected if btn.isSelected { btn.setTitle("停止", for: .normal) }else { btn.setTitle("旋转", for: .normal) } } func setUpPipeLineState() { var library = try? device.makeDefaultLibrary() if let url = Bundle.main.url(forResource: "CubeRender", withExtension: "metal"){ library = try? device.makeLibrary(URL: url) } let vfunc:MTLFunction? = library?.makeFunction(name: "cubeVertexShader") let fFunc:MTLFunction? = library?.makeFunction(name: "cubeFragmentShader") let description = MTLRenderPipelineDescriptor() description.vertexFunction = vfunc description.fragmentFunction = fFunc description.colorAttachments[0].pixelFormat = view.colorPixelFormat do { pipelineState = try device.makeRenderPipelineState(descriptor: description) } catch let error { print(error.localizedDescription) } commandQueue = device.makeCommandQueue() } func setUpVertex() { let vertexs1:[Float] = [ -0.5, 0.5, 0, 1.0, 0.5, 0.5, 0, 1.0, -0.5,-0.5, 0, 1.0, 0.5,-0.5, 0, 1.0, 0.0, 0.0, 0.5, 1 ] let vertexs3:[Float] = [ -0.5, 0.5, 0, 1.0,0, 1, 0.5, 0.5, 0, 1.0,1, 1, -0.5,-0.5, 0, 1.0,0, 0, 0.5,-0.5, 0, 1.0,1, 0, 0.0, 0.0, 1, 1,0.5,0.5 ] let vertexs2 : [CubeVertexTexture] = [ CubeVertexTexture(vex: vector_float4(-0.5, 0.5, 0, 1.0), tex: vector_float4(0, 1, 0, 0), color: vector_float4(1, 0,0,1)),//左上 CubeVertexTexture(vex: vector_float4( 0.5, 0.5, 0, 1.0), tex: vector_float4(1, 1, 0, 0), color: vector_float4(0, 1,0,1)),//右上 CubeVertexTexture(vex: vector_float4(-0.5,-0.5, 0, 1.0), tex: vector_float4(0, 0, 0, 0), color: vector_float4(0, 0,1,1)),//左下 CubeVertexTexture(vex: vector_float4( 0.5,-0.5, 0, 1.0), tex: vector_float4(1, 0, 0, 0), color: vector_float4(0, 1,1,1)),//右下 CubeVertexTexture(vex: vector_float4( 0.0, 0.0, 0.5, 1.0), tex: vector_float4(0.5,0.5,0,0), color: vector_float4(1, 1,0,1)) ] //坑:顶点坐标值的个数必须是4的倍数,vertexs1可以,vertexs3不行,vertexs2可以 vertexBuffer = device.makeBuffer(bytes: vertexs2, length: MemoryLayout<CubeVertexTexture>.size * (vertexs2.count), options: .storageModeShared) //索引 let index:[uint] = [ 0, 3, 2, 0, 1, 3, 0, 2, 4, 0, 4, 1, 2, 3, 4, 1, 4, 3 ] self.indexCount = index.count vertexIndex = device.makeBuffer(bytes: index, length: MemoryLayout<uint>.size * index.count, options: .storageModeShared) } func setUpImageTexture() { guard let image = UIImage(named: imageName)?.cgImage else { return } let width = image.width let height = image.height //开辟内存,绘制到这个内存上去 let spriteData: UnsafeMutablePointer = UnsafeMutablePointer<GLubyte>.allocate(capacity: MemoryLayout<GLubyte>.size * width * height * 4) UIGraphicsBeginImageContext(CGSize( width, height: height)) //获取context let spriteContext = CGContext(data: spriteData, width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: image.colorSpace!, bitmapInfo: image.bitmapInfo.rawValue) spriteContext?.translateBy(x:0 , y: CGFloat(height)) spriteContext?.scaleBy(x: 1, y: -1) spriteContext?.draw(image, in: CGRect(x: 0, y: 0, width, height: height)) UIGraphicsEndImageContext() // spriteData let textureDescriptor = MTLTextureDescriptor() textureDescriptor.pixelFormat = .rgba8Unorm //MTLPixelFormatRGBA8Unorm defoat textureDescriptor.width = image.width textureDescriptor.height = image.height texture = device.makeTexture(descriptor: textureDescriptor) texture?.replace(region: MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize( image.width, height: image.height, depth: 1)), mipmapLevel: 0, withBytes: spriteData, bytesPerRow: 4 * image.width) free(spriteData) } var x:Float = 0.0 var y:Float = 0.0 var z:Float = 0.0 //设置矩阵变换 func setMatrix(encode:MTLRenderCommandEncoder) { let size = self.view.bounds.size let perspectM = GLKMatrix4MakePerspective(Float.pi/2, Float(size.width/size.height), 0.1, 50.0) var modelViewM = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -2) if button.isSelected { if switchX.isOn { x += 1/180 * Float.pi } if switchY.isOn { y += 1/180 * Float.pi } if switchZ.isOn { z += 1/180 * Float.pi } } modelViewM = GLKMatrix4RotateX(modelViewM, x) modelViewM = GLKMatrix4RotateY(modelViewM, y) modelViewM = GLKMatrix4RotateZ(modelViewM, z) var matrix = DqMatrix(pMatix: perspectM.toMatrix_float4x4(), mvMatrix: modelViewM.toMatrix_float4x4()) encode.setVertexBytes(&matrix, length: MemoryLayout<DqMatrix>.size, index: 1) } } struct DqMatrix { var pMatix : matrix_float4x4 var mvMatrix :matrix_float4x4 } extension GLKMatrix4{ func toMatrix_float4x4() -> matrix_float4x4{ let matrix = self return matrix_float4x4( simd_make_float4(matrix.m00, matrix.m01, matrix.m02, matrix.m03), simd_make_float4(matrix.m10, matrix.m11, matrix.m12, matrix.m13), simd_make_float4(matrix.m20, matrix.m21, matrix.m22, matrix.m23), simd_make_float4(matrix.m30, matrix.m31, matrix.m32, matrix.m33) ) } } extension CubeRender :MTKViewDelegate{ func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let queue = commandQueue, let buffer = queue.makeCommandBuffer(), let renderPassDiscriptor = view.currentRenderPassDescriptor, let pipeState = pipelineState else { return } renderPassDiscriptor.colorAttachments[0].loadAction = .clear renderPassDiscriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.6, green: 0.2, blue: 0.5, alpha: 1) guard let encoder = buffer.makeRenderCommandEncoder(descriptor: renderPassDiscriptor) else { return } encoder.label = "renderEncoder" encoder.setRenderPipelineState(pipeState) encoder.setViewport(MTLViewport(originX: 0, originY: 0, Double(viewSize.width), height: Double(viewSize.height), znear: -1, zfar: 1)) encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) // encoder.setVertexBytes(vertexs1, length: MemoryLayout<Float>.size * vertexs1.count, index: 0) self.setMatrix(encode: encoder) encoder.setFragmentTexture(texture, index: 0) encoder.setFrontFacing(MTLWinding.counterClockwise) encoder.setCullMode(MTLCullMode.back) // encoder.drawPrimitives(type: MTLPrimitiveType.triangleStrip, vertexStart: 0, vertexCount: 5) encoder.drawIndexedPrimitives(type: .triangle, indexCount: self.indexCount, indexType: .uint32, indexBuffer: vertexIndex!, indexBufferOffset: 0)//使用索引绘图绘制 encoder.endEncoding() buffer.present(view.currentDrawable!) buffer.commit() } }
shader代码:
#include <metal_stdlib> using namespace metal; typedef struct{ float4 position; float4 textureCoord; float4 color; }VertexIn; typedef struct { float4 clipSpacePosition [[position]]; float2 textureCoord; float4 color; }VertexOut; typedef struct { float4x4 persMatrix; float4x4 mvMatrix; }MvpMatrix; vertex VertexOut cubeVertexShader(uint vertexIndex [[vertex_id]], constant VertexIn *ver [[buffer(0)]],constant MvpMatrix *matrixs [[buffer(1)]]){ VertexOut out; out.textureCoord = ver[vertexIndex].textureCoord.xy; out.clipSpacePosition = ver[vertexIndex].position; out.clipSpacePosition = matrixs->persMatrix * matrixs->mvMatrix * out.clipSpacePosition; out.color = ver[vertexIndex].color; return out; } fragment float4 cubeFragmentShader(VertexOut in [[stage_in]],texture2d<float> texture [[texture(0)]]){ constexpr sampler cubeSampler(mag_filter::linear,min_filter::linear); return texture.sample(cubeSampler, in.textureCoord); // return float4(0,1,0,1); // return in.color; }
Metal 提供以下特性:
- 低开销接口。Metal 被设计用于消灭像状态检查一类的隐性性能瓶颈,你可以控制 GPU 的异步行为,以实现用于并行创建和提交命令缓冲区的高效多线程操作
- 内存和资源管理。Metal 框架提供了表示 GPU 内存分配的缓冲区和纹理对象,纹理对象具有确切的像素格式,能被用于纹理图像或附件
- 集成对图形和计算操作的支持。Metal 对图形操作和计算操作使用了相同的数据结构和资源(如 buffer、texture、command queue),Metal 的着色器语言同时支持图形函数和计算函数,Metal 框架支持在运行时接口(CPU)、图形着色器和计算方法间共享资源
- 预编译着色器。Metal 的着色器函数能与代码一同在编译器编译,并在运行时加载,这样的流程能提供更方便的着色器调试功能。
demo详细代码github地址:https://github.com/duzhaoquan/MetalRender