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

参考文献:


原文地址:https://www.cnblogs.com/duzhaoquan/p/13305900.html