昨天 做完视频的 旋转 缩放 平移 正交投影后 花了一天的时间 简单的看了下GPUImage3的整个框架 想看看能自己结合GUPImage3的思路 想出一个套更加 合理的多级渲染协议 所以就简单的 过了一遍GPUImage3的基础源码 总结如下
字写的不好看 有读者的话 就凑合一下吧 只是自己做笔记当备忘录的
下面是主要的几个类 和 协议
1. 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) {
// if targets.count == 0 { // Deal with the case where no targets are attached by immediately returning framebuffer to cache
// framebuffer.lock()
// framebuffer.unlock()
// } else {
// // Lock first for each output, to guarantee proper ordering on multi-output operations
// for _ in targets {
// framebuffer.lock()
// }
// }
for (target, index) in targets {
target.newTextureAvailable(texture, fromSourceIndex:index)
}
}
}
需要注意的是:
public protocol ImageSource {
var targets:TargetContainer { get }
func transmitPreviousImage(to target:ImageConsumer, atIndex:UInt)
}
2. ImageConsumer
public protocol ImageConsumer:AnyObject {
var maximumInputs:UInt { get }
var sources:SourceContainer { get }
func newTextureAvailable(_ texture:Texture, fromSourceIndex:UInt)
}
3. TargetContainer
class WeakImageConsumer {
weak var value:ImageConsumer?
let indexAtTarget:UInt
init (value:ImageConsumer, indexAtTarget:UInt) {
self.indexAtTarget = indexAtTarget
self.value = value
}
}
var targets = [WeakImageConsumer]()
public class TargetContainer:Sequence {
var targets = [WeakImageConsumer]()
var count:Int { get {return targets.count}}
let dispatchQueue = DispatchQueue(label:"com.sunsetlakesoftware.GPUImage.targetContainerQueue", attributes: [])
public init() {
}
public func append(_ target:ImageConsumer, indexAtTarget:UInt) {
// TODO: Don't allow the addition of a target more than once
dispatchQueue.async{
self.targets.append(WeakImageConsumer(value:target, indexAtTarget:indexAtTarget))
}
}
public func makeIterator() -> AnyIterator<(ImageConsumer, UInt)> {
var index = 0
return AnyIterator { () -> (ImageConsumer, UInt)? in
return self.dispatchQueue.sync{
if (index >= self.targets.count) {
return nil
}
while (self.targets[index].value == nil) {
self.targets.remove(at:index)
if (index >= self.targets.count) {
return nil
}
}
index += 1
return (self.targets[index - 1].value!, self.targets[index - 1].indexAtTarget)
}
}
}
public func removeAll() {
dispatchQueue.async{
self.targets.removeAll()
}
}
}
4. SourceContainer
public class SourceContainer {
var sources:[UInt:ImageSource] = [:]
public init() {
}
public func append(_ source:ImageSource, maximumInputs:UInt) -> UInt? {
var currentIndex:UInt = 0
while currentIndex < maximumInputs {
if (sources[currentIndex] == nil) {
sources[currentIndex] = source
return currentIndex
}
currentIndex += 1
}
return nil
}
public func insert(_ source:ImageSource, atIndex:UInt, maximumInputs:UInt) -> UInt {
guard (atIndex < maximumInputs) else { fatalError("ERROR: Attempted to set a source beyond the maximum number of inputs on this operation") }
sources[atIndex] = source
return atIndex
}
public func removeAtIndex(_ index:UInt) {
sources[index] = nil
}
}
5. BasicOperation
public protocol ImageProcessingOperation: ImageConsumer, ImageSource
{
}
open class BasicOperation: ImageProcessingOperation {
public let maximumInputs: UInt
public let targets = TargetContainer()
public let sources = SourceContainer()
public var activatePassthroughOnNextFrame: Bool = false
public var uniformSettings:ShaderUniformSettings
public var useMetalPerformanceShaders: Bool = false {
didSet {
if !sharedMetalRenderingDevice.metalPerformanceShadersAreSupported {
print("Warning: Metal Performance Shaders are not supported on this device")
useMetalPerformanceShaders = false
}
}
}
let renderPipelineState: MTLRenderPipelineState
let operationName: String
var inputTextures = [UInt:Texture]()
let textureInputSemaphore = DispatchSemaphore(value:1)
var useNormalizedTextureCoordinates = true
var metalPerformanceShaderPathway: ((MTLCommandBuffer, [UInt:Texture], Texture) -> ())?
public init(vertexFunctionName: String? = nil, fragmentFunctionName: String, numberOfInputs: UInt = 1, operationName: String = #file) {
self.maximumInputs = numberOfInputs
self.operationName = operationName
let concreteVertexFunctionName = vertexFunctionName ?? defaultVertexFunctionNameForInputs(numberOfInputs)
let (pipelineState, lookupTable) = generateRenderPipelineState(device:sharedMetalRenderingDevice, vertexFunctionName:concreteVertexFunctionName, fragmentFunctionName:fragmentFunctionName, operationName:operationName)
self.renderPipelineState = pipelineState
self.uniformSettings = ShaderUniformSettings(uniformLookupTable:lookupTable)
}
public func transmitPreviousImage(to target: ImageConsumer, atIndex: UInt) {
// TODO: Finish implementation later
}
public func newTextureAvailable(_ texture: Texture, fromSourceIndex: UInt) {
let _ = textureInputSemaphore.wait(timeout:DispatchTime.distantFuture)
defer {
textureInputSemaphore.signal()
}
inputTextures[fromSourceIndex] = texture
if (UInt(inputTextures.count) >= maximumInputs) || activatePassthroughOnNextFrame {
let outputWidth:Int
let outputHeight:Int
let firstInputTexture = inputTextures[0]!
if firstInputTexture.orientation.rotationNeeded(for:.portrait).flipsDimensions() {
outputWidth = firstInputTexture.texture.height
outputHeight = firstInputTexture.texture.width
} else {
outputWidth = firstInputTexture.texture.width
outputHeight = firstInputTexture.texture.height
}
if uniformSettings.usesAspectRatio {
let outputRotation = firstInputTexture.orientation.rotationNeeded(for:.portrait)
uniformSettings["aspectRatio"] = firstInputTexture.aspectRatio(for: outputRotation)
}
guard let commandBuffer = sharedMetalRenderingDevice.commandQueue.makeCommandBuffer() else {return}
let outputTexture = Texture(device:sharedMetalRenderingDevice.device, orientation: .portrait, width: outputWidth, height: outputHeight, timingStyle: firstInputTexture.timingStyle)
guard (!activatePassthroughOnNextFrame) else { // Use this to allow a bootstrap of cyclical processing, like with a low pass filter
activatePassthroughOnNextFrame = false
// TODO: Render rotated passthrough image here
removeTransientInputs()
textureInputSemaphore.signal()
updateTargetsWithTexture(outputTexture)
let _ = textureInputSemaphore.wait(timeout:DispatchTime.distantFuture)
return
}
if let alternateRenderingFunction = metalPerformanceShaderPathway, useMetalPerformanceShaders {
var rotatedInputTextures: [UInt:Texture]
if (firstInputTexture.orientation.rotationNeeded(for:.portrait) != .noRotation) {
let rotationOutputTexture = Texture(device:sharedMetalRenderingDevice.device, orientation: .portrait, width: outputWidth, height: outputHeight)
guard let rotationCommandBuffer = sharedMetalRenderingDevice.commandQueue.makeCommandBuffer() else {return}
rotationCommandBuffer.renderQuad(pipelineState: sharedMetalRenderingDevice.passthroughRenderState, uniformSettings: uniformSettings, inputTextures: inputTextures, useNormalizedTextureCoordinates: useNormalizedTextureCoordinates, outputTexture: rotationOutputTexture)
rotationCommandBuffer.commit()
rotatedInputTextures = inputTextures
rotatedInputTextures[0] = rotationOutputTexture
} else {
rotatedInputTextures = inputTextures
}
alternateRenderingFunction(commandBuffer, rotatedInputTextures, outputTexture)
} else {
internalRenderFunction(commandBuffer: commandBuffer, outputTexture: outputTexture)
}
commandBuffer.commit()
removeTransientInputs()
textureInputSemaphore.signal()
updateTargetsWithTexture(outputTexture)
let _ = textureInputSemaphore.wait(timeout:DispatchTime.distantFuture)
}
}
6. 操作符 重载
@discardableResult public func --><T:ImageConsumer>(source:ImageSource, destination:T) -> T {
source.addTarget(destination)
return destination
}