GPUImage3(一)

因为最新在学习Metal,所以就去看了一下GPUImage3,之前的2个版本都是基于openGL ES,到了GPUImage3就改成了基于Metal的实现。

我们现在就开始来了解一下GPUImage3框架的设计以及一些核心类的实现。

image.png

整体框架就分为4分部分 BaseInputsOutputsOperations

Base:工具类
Inputs:输入源
Outputs:输出源
Operations:滤镜

Base

常用数据类:ImageOrientationColor, Position, Size, Matrix, Timestamp, Texture
常用工具类:Pipeline, MetalRenderingDevice, ShaderUniformSettings

我们来说几个关键的类:

1.MetalRenderingDevice:

它里面使用了单例,维护着一些频繁使用的Metal对象

public let sharedMetalRenderingDevice = MetalRenderingDevice()

public class MetalRenderingDevice {
public let device: MTLDevice
public let commandQueue: MTLCommandQueue
public let shaderLibrary: MTLLibrary

...

init() {
        guard let device = MTLCreateSystemDefaultDevice() else {fatalError("Could not create Metal Device")}
        self.device = device
        
        guard let queue = self.device.makeCommandQueue() else {fatalError("Could not create command queue")}
        self.commandQueue = queue
        
        if #available(iOS 9, macOS 10.13, *) {
            self.metalPerformanceShadersAreSupported = MPSSupportsMTLDevice(device)
        } else {
            self.metalPerformanceShadersAreSupported = false
        }
        
        do {
            let frameworkBundle = Bundle(for: MetalRenderingDevice.self)
            let metalLibraryPath = frameworkBundle.path(forResource: "default", ofType: "metallib")!
            
            self.shaderLibrary = try device.makeLibrary(filepath:metalLibraryPath)
        } catch {
            fatalError("Could not load library")
        }
    }

}

MTLDevice : 能够进行数据并行计算的处理器。代表GPU。通过MTLCreateSystemDefaultDevice()获取到默认的GPU
MTLCommandQueue:由device对象创建,device.makeCommandQueue(),用于指令能够有序的传送到GPU
MTLLibrary:一个着色器功能的集合,包含了在程序运行时编译的着色器源代码。由device创建,device.makeDefaultLibrary()。获取默认的library对象

MTLDevice, MTLCommandQueue, MTLLibrary这些对象创建一次就够了,创建比较繁重,可重复使用

2.MetalRendering:

在这个文件里面有两个地方。
1.MTLCommandBuffer的扩展:

extension MTLCommandBuffer {
    /// 清屏
    func clear(with color: Color, outputTexture: Texture) {
        let renderPass = MTLRenderPassDescriptor()
        renderPass.colorAttachments[0].texture = outputTexture.texture
        renderPass.colorAttachments[0].clearColor = MTLClearColorMake(Double(color.redComponent), Double(color.greenComponent), Double(color.blueComponent), Double(color.alphaComponent))
        renderPass.colorAttachments[0].storeAction = .store
        renderPass.colorAttachments[0].loadAction = .clear
        
        print("Clear color: \(renderPass.colorAttachments[0].clearColor)")
        
        guard let renderEncoder = self.makeRenderCommandEncoder(descriptor: renderPass) else {
            fatalError("Could not create render encoder")
        }
//        renderEncoder.setRenderPipelineState(sharedMetalRenderingDevice.passthroughRenderState)

//        renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 0)

        renderEncoder.endEncoding()
    }
    
    //传入对应的配置项来完成渲染指令的提交
    func renderQuad(pipelineState:MTLRenderPipelineState, //渲染管线
                    uniformSettings:ShaderUniformSettings? = nil, //常量
                    inputTextures:[UInt:Texture], // 纹理数据
                    useNormalizedTextureCoordinates:Bool = true,
                    imageVertices:[Float] = standardImageVertices, //顶点坐标
                    outputTexture:Texture, // 目标纹理
                    outputOrientation:ImageOrientation = .portrait) {
        /// 生成顶点数据的buffer
        let vertexBuffer = sharedMetalRenderingDevice.device.makeBuffer(bytes: imageVertices,
                                                                        length: imageVertices.count * MemoryLayout<Float>.size,
                                                                        options: [])!
        vertexBuffer.label = "Vertices"
        
        
        let renderPass = MTLRenderPassDescriptor()
        renderPass.colorAttachments[0].texture = outputTexture.texture
        renderPass.colorAttachments[0].clearColor = MTLClearColorMake(1, 0, 0, 1)
        renderPass.colorAttachments[0].storeAction = .store
        renderPass.colorAttachments[0].loadAction = .clear
        
        guard let renderEncoder = self.makeRenderCommandEncoder(descriptor: renderPass) else {
            fatalError("Could not create render encoder")
        }
        renderEncoder.setFrontFacing(.counterClockwise) //逆时针
        renderEncoder.setRenderPipelineState(pipelineState) //设置渲染管线
        renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) //设置顶点数据
        
        for textureIndex in 0..<inputTextures.count {
            let currentTexture = inputTextures[UInt(textureIndex)]!
            
            //纹理坐标数据
            let inputTextureCoordinates = currentTexture.textureCoordinates(for:outputOrientation, normalized:useNormalizedTextureCoordinates)
            let textureBuffer = sharedMetalRenderingDevice.device.makeBuffer(bytes: inputTextureCoordinates,
                                                                             length: inputTextureCoordinates.count * MemoryLayout<Float>.size,
                                                                             options: [])!
            textureBuffer.label = "Texture Coordinates"

            //传纹理坐标数据
            renderEncoder.setVertexBuffer(textureBuffer, offset: 0, index: 1 + textureIndex)
            //传纹理数据
            renderEncoder.setFragmentTexture(currentTexture.texture, index: textureIndex)
        }
        
        //传常量数据
        uniformSettings?.restoreShaderSettings(renderEncoder: renderEncoder)
        ///绘制基本图元形状
        renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
        /// 结束编码
        renderEncoder.endEncoding()
    }
}

里面又2个方法:
1.clear 清屏
2.renderQuad传入指定的参数配置生成encoder指令

第二个方法
通过传入device, vertexFunctionName, fragmentFunctionName 来生成 MTLRenderPipelineState

//通过传入device, vertexFunctionName, fragmentFunctionName 来生成 MTLRenderPipelineState
func generateRenderPipelineState(device:MetalRenderingDevice,
                                 vertexFunctionName:String,
                                 fragmentFunctionName:String,
                                 operationName:String) -> (MTLRenderPipelineState, [String:(Int, MTLDataType)]) {
    //顶点着色器
    guard let vertexFunction = device.shaderLibrary.makeFunction(name: vertexFunctionName) else {
        fatalError("\(operationName): could not compile vertex function \(vertexFunctionName)")
    }
    
    //片段着色器
    guard let fragmentFunction = device.shaderLibrary.makeFunction(name: fragmentFunctionName) else {
        fatalError("\(operationName): could not compile fragment function \(fragmentFunctionName)")
    }
    
    let descriptor = MTLRenderPipelineDescriptor()
    descriptor.colorAttachments[0].pixelFormat = MTLPixelFormat.bgra8Unorm
    descriptor.rasterSampleCount = 1
    descriptor.vertexFunction = vertexFunction
    descriptor.fragmentFunction = fragmentFunction
    
    do {
        var reflection:MTLAutoreleasedRenderPipelineReflection?
        let pipelineState = try device.device.makeRenderPipelineState(descriptor: descriptor, options: [.bufferTypeInfo, .argumentInfo], reflection: &reflection)

        var uniformLookupTable:[String:(Int, MTLDataType)] = [:]
        if let fragmentArguments = reflection?.fragmentArguments {
            for fragmentArgument in fragmentArguments where fragmentArgument.type == .buffer {
                if
                  (fragmentArgument.bufferDataType == .struct),
                  let members = fragmentArgument.bufferStructType?.members.enumerated() {
                    for (index, uniform) in members {
                        uniformLookupTable[uniform.name] = (index, uniform.dataType)
                    }
                }
            }
        }
        
        return (pipelineState, uniformLookupTable)
    } catch {
        fatalError("Could not create render pipeline state for vertex:\(vertexFunctionName), fragment:\(fragmentFunctionName), error:\(error)")
    }
}
3.Pipeline

里面定义了一些协议,自定义操作符,生产/消费者对象

首先来看下 ImageSource

public protocol ImageSource {
    var targets:TargetContainer { get }
    func transmitPreviousImage(to target:ImageConsumer, atIndex:UInt)
}

数据源协议,具有输出功能。一般遵循的类Inputs文件下,还有滤镜,滤镜具有输入输出功能
var targets:TargetContainer 记录要输出的目标
func transmitPreviousImage 该方法传递纹理到指定的目标

再来看下 ImageSource扩展

public extension ImageSource {
    func addTarget(_ target:ImageConsumer, atTargetIndex:UInt? = nil) {
        if let targetIndex = atTargetIndex {
            target.setSource(self, atIndex:targetIndex)
            targets.append(target, indexAtTarget:targetIndex)
            transmitPreviousImage(to:target, atIndex:targetIndex)
        } else if let indexAtTarget = target.addSource(self) {
            targets.append(target, indexAtTarget:indexAtTarget)
            transmitPreviousImage(to:target, atIndex:indexAtTarget)
        } else {
            debugPrint("Warning: tried to add target beyond target's input capacity")
        }
    }

    func removeAllTargets() {
        for (target, index) in targets {
            target.removeSourceAtIndex(index)
        }
        targets.removeAll()
    }
    
    func updateTargetsWithTexture(_ texture:Texture) {
        for (target, index) in targets {
            target.newTextureAvailable(texture, fromSourceIndex:index)
        }
    }
}

addTarget:添加目标到targets里面,并且添加的时候会调用transmitPreviousImage
removeAllTargets:移除所有记录的目标
updateTargetsWithTexture: 为记录的所有目标更新纹理数据

接下来我们来看 ImageConsumer

public protocol ImageConsumer:AnyObject {
    var maximumInputs:UInt { get }
    var sources:SourceContainer { get }
    
    func newTextureAvailable(_ texture:Texture, fromSourceIndex:UInt)
}

消费者,顾名思义就是接受纹理信息的,具有输入功能。一般遵循的类Outputs文件下,还有滤镜,滤镜具有输入输出功能

maximumInputs:最大的输入
sources:记录数据的来源,就是遵循ImageSource传递过来的对象,
newTextureAvailable:接受更新的纹理数据信息

ImageConsumer的扩展,跟ImageSource差不多,是对数据来源的添加、移除的操作

public extension ImageConsumer {
    func addSource(_ source:ImageSource) -> UInt? {
        return sources.append(source, maximumInputs:maximumInputs)
    }
    
    func setSource(_ source:ImageSource, atIndex:UInt) {
        _ = sources.insert(source, atIndex:atIndex, maximumInputs:maximumInputs)
    }

    func removeSourceAtIndex(_ index:UInt) {
        sources.removeAtIndex(index)
    }
}

再来看看ImageProcessingOperation,具有输入、输出能力,那么它就是滤镜了,滤镜就遵循这个协议

public protocol ImageProcessingOperation: ImageConsumer, ImageSource {
}

GPUImage3充分发挥的swift的特性,接下来看自定义操作符

infix operator --> : AdditionPrecedence
//precedencegroup ProcessingOperationPrecedence {
//    associativity: left
////    higherThan: Multiplicative
//}
@discardableResult public func --><T:ImageConsumer>(source:ImageSource, destination:T) -> T {
    source.addTarget(destination)
    return destination
}

通过自定义操作符,将输入、输入通过-->链接起来,形成一个滤镜链
举个例子:

pictureInput = PictureInput(image: UIImage(named: "image_5.jpg")!)
stretchFilter = StretchFilter()
pictureInput --> stretchFilter --> renderView
pictureInput.processImage()

通过-->的链接,遵循ImageSourceImageConsumer就可以一直链接下去。


暂时就记录这么多,我也在探索中,仅仅以此记录自己的学习。

热爱生活,享受生活!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容