|
|
@@ -0,0 +1,1072 @@
|
|
|
+import Foundation
|
|
|
+import UIKit
|
|
|
+import CoreVideo
|
|
|
+import CoreImage
|
|
|
+import QuartzCore
|
|
|
+import Metal
|
|
|
+import MetalKit
|
|
|
+import simd
|
|
|
+
|
|
|
+@objc public protocol LNHWDMetalViewDelegate: AnyObject {
|
|
|
+ @objc(onMetalViewUnavailable)
|
|
|
+ func onMetalViewUnavailable()
|
|
|
+}
|
|
|
+
|
|
|
+@objc public protocol LNVAPMetalViewDelegate: AnyObject {
|
|
|
+ @objc(onMetalViewUnavailable)
|
|
|
+ func onMetalViewUnavailable()
|
|
|
+}
|
|
|
+
|
|
|
+@objc public protocol LNHWDMP4OpenGLViewDelegate: AnyObject {
|
|
|
+ @objc(onViewUnavailableStatus)
|
|
|
+ func onViewUnavailableStatus()
|
|
|
+}
|
|
|
+
|
|
|
+private enum LNLegacyRuntime {
|
|
|
+ static func cls(_ name: String) -> AnyClass? {
|
|
|
+ NSClassFromString(name) ?? NSClassFromString("QGVAPlayer.\(name)")
|
|
|
+ }
|
|
|
+
|
|
|
+ static func instantiate(_ className: String) -> NSObject? {
|
|
|
+ guard let t = cls(className) as? NSObject.Type else { return nil }
|
|
|
+ return t.init()
|
|
|
+ }
|
|
|
+
|
|
|
+ static func set(_ obj: NSObject, _ key: String, _ value: Any?) {
|
|
|
+ let setter = "set\(key.prefix(1).uppercased())\(key.dropFirst()):"
|
|
|
+ let sel = NSSelectorFromString(setter)
|
|
|
+ guard obj.responds(to: sel) else { return }
|
|
|
+ obj.setValue(value, forKey: key)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callInitWithFrame(_ obj: NSObject, frame: CGRect) -> AnyObject? {
|
|
|
+ let sel = NSSelectorFromString("initWithFrame:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return nil }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, CGRect) -> AnyObject
|
|
|
+ return unsafeBitCast(imp, to: Fn.self)(obj, sel, frame)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callInitWithFrameBlend(_ obj: NSObject, frame: CGRect, blend: Int) -> AnyObject? {
|
|
|
+ let sel = NSSelectorFromString("initWithFrame:blendMode:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return nil }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, CGRect, Int) -> AnyObject
|
|
|
+ return unsafeBitCast(imp, to: Fn.self)(obj, sel, frame, blend)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callInitWithMetalLayer(_ obj: NSObject, layer: AnyObject) -> AnyObject? {
|
|
|
+ let sel = NSSelectorFromString("initWithMetalLayer:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return nil }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, AnyObject) -> AnyObject
|
|
|
+ return unsafeBitCast(imp, to: Fn.self)(obj, sel, layer)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callInitWithMetalLayerBlend(_ obj: NSObject, layer: AnyObject, blend: Int) -> AnyObject? {
|
|
|
+ let sel = NSSelectorFromString("initWithMetalLayer:blendMode:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return nil }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, AnyObject, Int) -> AnyObject
|
|
|
+ return unsafeBitCast(imp, to: Fn.self)(obj, sel, layer, blend)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callRenderPixelBuffer(_ obj: NSObject, pixelBuffer: CVPixelBuffer?, layer: AnyObject?) {
|
|
|
+ let sel = NSSelectorFromString("renderPixelBuffer:metalLayer:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, CVPixelBuffer?, AnyObject?) -> Void
|
|
|
+ unsafeBitCast(imp, to: Fn.self)(obj, sel, pixelBuffer, layer)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callRenderPixelBufferMerge(_ obj: NSObject, pixelBuffer: CVPixelBuffer?, layer: AnyObject?, infos: NSArray?) {
|
|
|
+ let sel = NSSelectorFromString("renderPixelBuffer:metalLayer:mergeInfos:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, CVPixelBuffer?, AnyObject?, NSArray?) -> Void
|
|
|
+ unsafeBitCast(imp, to: Fn.self)(obj, sel, pixelBuffer, layer, infos)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callDisplay(_ obj: NSObject, pixelBuffer: CVPixelBuffer?) {
|
|
|
+ let sel = NSSelectorFromString("display:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, CVPixelBuffer?) -> Void
|
|
|
+ unsafeBitCast(imp, to: Fn.self)(obj, sel, pixelBuffer)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callDisplayMerge(_ obj: NSObject, pixelBuffer: CVPixelBuffer?, infos: NSArray?) {
|
|
|
+ let sel = NSSelectorFromString("display:mergeInfos:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, CVPixelBuffer?, NSArray?) -> Void
|
|
|
+ unsafeBitCast(imp, to: Fn.self)(obj, sel, pixelBuffer, infos)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callDisplayPixelBuffer(_ obj: NSObject, pixelBuffer: CVPixelBuffer?) {
|
|
|
+ let sel = NSSelectorFromString("displayPixelBuffer:")
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector, CVPixelBuffer?) -> Void
|
|
|
+ unsafeBitCast(imp, to: Fn.self)(obj, sel, pixelBuffer)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func callNoArgs(_ obj: NSObject, _ selector: String) {
|
|
|
+ let sel = NSSelectorFromString(selector)
|
|
|
+ guard obj.responds(to: sel), let imp = obj.method(for: sel) else { return }
|
|
|
+ typealias Fn = @convention(c) (AnyObject, Selector) -> Void
|
|
|
+ unsafeBitCast(imp, to: Fn.self)(obj, sel)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private final class LNPixelBufferLayerRenderer {
|
|
|
+ private let ciContext = CIContext(options: nil)
|
|
|
+
|
|
|
+ func render(pixelBuffer: CVPixelBuffer, into layer: CALayer) {
|
|
|
+ let image = CIImage(cvPixelBuffer: pixelBuffer)
|
|
|
+ guard let cgImage = ciContext.createCGImage(image, from: image.extent) else { return }
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ layer.contents = cgImage
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@available(iOS 13.0, *)
|
|
|
+private final class LNHWDMetalCoreRenderer {
|
|
|
+ private struct LNColorParameters {
|
|
|
+ var matrix: simd_float3x3
|
|
|
+ var offset: simd_float2
|
|
|
+ }
|
|
|
+
|
|
|
+ private static let kVertexFunctionName = "hwd_vertexShader"
|
|
|
+ private static let kFragmentFunctionName = "hwd_yuvFragmentShader"
|
|
|
+
|
|
|
+ private static let verticesByBlendMode: [[[Float]]] = [
|
|
|
+ // alpha left
|
|
|
+ [
|
|
|
+ [-1.0, -1.0, 0.0, 1.0, 0.5, 1.0, 0.0, 1.0],
|
|
|
+ [-1.0, 1.0, 0.0, 1.0, 0.5, 0.0, 0.0, 0.0],
|
|
|
+ [ 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.5, 1.0],
|
|
|
+ [ 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.5, 0.0]
|
|
|
+ ],
|
|
|
+ // alpha right
|
|
|
+ [
|
|
|
+ [-1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.5, 1.0],
|
|
|
+ [-1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0],
|
|
|
+ [ 1.0, -1.0, 0.0, 1.0, 0.5, 1.0, 1.0, 1.0],
|
|
|
+ [ 1.0, 1.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0]
|
|
|
+ ],
|
|
|
+ // alpha top
|
|
|
+ [
|
|
|
+ [-1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5],
|
|
|
+ [-1.0, 1.0, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0],
|
|
|
+ [ 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.5],
|
|
|
+ [ 1.0, 1.0, 0.0, 1.0, 1.0, 0.5, 1.0, 0.0]
|
|
|
+ ],
|
|
|
+ // alpha bottom
|
|
|
+ [
|
|
|
+ [-1.0, -1.0, 0.0, 1.0, 0.0, 0.5, 0.0, 1.0],
|
|
|
+ [-1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.5],
|
|
|
+ [ 1.0, -1.0, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0],
|
|
|
+ [ 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.5]
|
|
|
+ ]
|
|
|
+ ]
|
|
|
+
|
|
|
+ private static let matrix601Full = simd_float3x3(
|
|
|
+ SIMD3<Float>(1.0, 1.0, 1.0),
|
|
|
+ SIMD3<Float>(0.0, -0.34413, 1.772),
|
|
|
+ SIMD3<Float>(1.402, -0.71414, 0.0)
|
|
|
+ )
|
|
|
+
|
|
|
+ private static let matrix709Full = simd_float3x3(
|
|
|
+ SIMD3<Float>(1.0, 1.0, 1.0),
|
|
|
+ SIMD3<Float>(0.0, -0.18732, 1.8556),
|
|
|
+ SIMD3<Float>(1.57481, -0.46813, 0.0)
|
|
|
+ )
|
|
|
+
|
|
|
+ private let device: MTLDevice
|
|
|
+ private let commandQueue: MTLCommandQueue
|
|
|
+ private let pipelineState: MTLRenderPipelineState
|
|
|
+ private var textureCache: CVMetalTextureCache?
|
|
|
+ private var vertexBuffer: MTLBuffer?
|
|
|
+ private var yuvMatrixBuffer: MTLBuffer?
|
|
|
+ private var currentColorMatrix = matrix601Full
|
|
|
+
|
|
|
+ init?(metalLayer: CAMetalLayer, blendMode: QGHWDTextureBlendMode) {
|
|
|
+ guard let device = MTLCreateSystemDefaultDevice(),
|
|
|
+ let queue = device.makeCommandQueue(),
|
|
|
+ let library = device.makeDefaultLibrary(),
|
|
|
+ let vertexFunc = library.makeFunction(name: Self.kVertexFunctionName),
|
|
|
+ let fragmentFunc = library.makeFunction(name: Self.kFragmentFunctionName) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ let desc = MTLRenderPipelineDescriptor()
|
|
|
+ desc.vertexFunction = vertexFunc
|
|
|
+ desc.fragmentFunction = fragmentFunc
|
|
|
+ desc.colorAttachments[0].pixelFormat = metalLayer.pixelFormat
|
|
|
+ guard let pipelineState = try? device.makeRenderPipelineState(descriptor: desc) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ self.device = device
|
|
|
+ self.commandQueue = queue
|
|
|
+ self.pipelineState = pipelineState
|
|
|
+
|
|
|
+ metalLayer.device = device
|
|
|
+ metalLayer.framebufferOnly = false
|
|
|
+
|
|
|
+ let cacheStatus = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache)
|
|
|
+ guard cacheStatus == kCVReturnSuccess else { return nil }
|
|
|
+
|
|
|
+ updateBlendMode(blendMode)
|
|
|
+ updateYuvMatrixBuffer(matrix: Self.matrix601Full)
|
|
|
+ }
|
|
|
+
|
|
|
+ func updateBlendMode(_ blendMode: QGHWDTextureBlendMode) {
|
|
|
+ let modeIndex = max(0, min(Int(blendMode.rawValue), Self.verticesByBlendMode.count - 1))
|
|
|
+ let flat = Self.verticesByBlendMode[modeIndex].flatMap { $0 }
|
|
|
+ vertexBuffer = device.makeBuffer(bytes: flat, length: flat.count * MemoryLayout<Float>.size, options: .storageModeShared)
|
|
|
+ }
|
|
|
+
|
|
|
+ func render(pixelBuffer: CVPixelBuffer, metalLayer: CAMetalLayer) {
|
|
|
+ guard metalLayer.bounds.width > 0,
|
|
|
+ metalLayer.bounds.height > 0,
|
|
|
+ let drawable = metalLayer.nextDrawable(),
|
|
|
+ let commandBuffer = commandQueue.makeCommandBuffer(),
|
|
|
+ let descriptor = currentRenderPassDescriptor(texture: drawable.texture),
|
|
|
+ let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor),
|
|
|
+ let vertexBuffer,
|
|
|
+ let yuvMatrixBuffer else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ updateYuvMatrixIfNeeded(pixelBuffer: pixelBuffer)
|
|
|
+ guard let yTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 0, format: .r8Unorm),
|
|
|
+ let uvTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 1, format: .rg8Unorm) else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ encoder.setRenderPipelineState(pipelineState)
|
|
|
+ encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentTexture(yTexture, index: 0)
|
|
|
+ encoder.setFragmentTexture(uvTexture, index: 1)
|
|
|
+ encoder.drawPrimitives(type: MTLPrimitiveType.triangleStrip, vertexStart: 0, vertexCount: 4)
|
|
|
+ encoder.endEncoding()
|
|
|
+
|
|
|
+ commandBuffer.present(drawable)
|
|
|
+ commandBuffer.commit()
|
|
|
+ }
|
|
|
+
|
|
|
+ func dispose() {
|
|
|
+ if let textureCache {
|
|
|
+ CVMetalTextureCacheFlush(textureCache, 0)
|
|
|
+ }
|
|
|
+ textureCache = nil
|
|
|
+ vertexBuffer = nil
|
|
|
+ yuvMatrixBuffer = nil
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeTexture(pixelBuffer: CVPixelBuffer, plane: Int, format: MTLPixelFormat) -> MTLTexture? {
|
|
|
+ guard let textureCache else { return nil }
|
|
|
+ let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, plane)
|
|
|
+ let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane)
|
|
|
+ var cvTexture: CVMetalTexture?
|
|
|
+ let status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
|
|
|
+ textureCache,
|
|
|
+ pixelBuffer,
|
|
|
+ nil,
|
|
|
+ format,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ plane,
|
|
|
+ &cvTexture)
|
|
|
+ guard status == kCVReturnSuccess, let cvTexture else { return nil }
|
|
|
+ return CVMetalTextureGetTexture(cvTexture)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func currentRenderPassDescriptor(texture: MTLTexture) -> MTLRenderPassDescriptor? {
|
|
|
+ let descriptor = MTLRenderPassDescriptor()
|
|
|
+ descriptor.colorAttachments[0].texture = texture
|
|
|
+ descriptor.colorAttachments[0].loadAction = .clear
|
|
|
+ descriptor.colorAttachments[0].storeAction = .store
|
|
|
+ descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
|
|
|
+ return descriptor
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateYuvMatrixIfNeeded(pixelBuffer: CVPixelBuffer) {
|
|
|
+ let matrixAttachment = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, nil)?.takeUnretainedValue()
|
|
|
+ let matrix = (matrixAttachment != nil && CFEqual(matrixAttachment, kCVImageBufferYCbCrMatrix_ITU_R_709_2)) ? Self.matrix709Full : Self.matrix601Full
|
|
|
+ guard matrix != currentColorMatrix else { return }
|
|
|
+ currentColorMatrix = matrix
|
|
|
+ updateYuvMatrixBuffer(matrix: matrix)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateYuvMatrixBuffer(matrix: simd_float3x3) {
|
|
|
+ let params = LNColorParameters(matrix: matrix, offset: simd_float2(0.5, 0.5))
|
|
|
+ yuvMatrixBuffer = device.makeBuffer(bytes: [params], length: MemoryLayout<LNColorParameters>.stride, options: .storageModeShared)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@available(iOS 13.0, *)
|
|
|
+private final class LNVAPMetalCoreRenderer {
|
|
|
+ private struct LNColorParameters {
|
|
|
+ var matrix: simd_float3x3
|
|
|
+ var offset: simd_float2
|
|
|
+ }
|
|
|
+
|
|
|
+ private struct LNMaskParameters {
|
|
|
+ var weightMatrix: simd_float3x3
|
|
|
+ var coreSize: Int32
|
|
|
+ var texelOffset: Float
|
|
|
+ }
|
|
|
+
|
|
|
+ private static let matrix601Full = simd_float3x3(
|
|
|
+ SIMD3<Float>(1.0, 1.0, 1.0),
|
|
|
+ SIMD3<Float>(0.0, -0.34413, 1.772),
|
|
|
+ SIMD3<Float>(1.402, -0.71414, 0.0)
|
|
|
+ )
|
|
|
+
|
|
|
+ private static let matrix709Full = simd_float3x3(
|
|
|
+ SIMD3<Float>(1.0, 1.0, 1.0),
|
|
|
+ SIMD3<Float>(0.0, -0.18732, 1.8556),
|
|
|
+ SIMD3<Float>(1.57481, -0.46813, 0.0)
|
|
|
+ )
|
|
|
+
|
|
|
+ private let device: MTLDevice
|
|
|
+ private let pixelFormat: MTLPixelFormat
|
|
|
+ private let commandQueue: MTLCommandQueue
|
|
|
+ private let shaderLoader: LNVAPMetalShaderFunctionLoader
|
|
|
+ private var textureCache: CVMetalTextureCache?
|
|
|
+ private var defaultMainPipelineState: MTLRenderPipelineState?
|
|
|
+ private var mainPipelineStateForMask: MTLRenderPipelineState?
|
|
|
+ private var mainPipelineStateForMaskBlur: MTLRenderPipelineState?
|
|
|
+ private var attachmentPipelineState: MTLRenderPipelineState?
|
|
|
+
|
|
|
+ private var vertexBuffer: MTLBuffer?
|
|
|
+ private var yuvMatrixBuffer: MTLBuffer?
|
|
|
+ private var maskBlurBuffer: MTLBuffer?
|
|
|
+ private var maskTexture: MTLTexture?
|
|
|
+ private var currentColorMatrix = matrix601Full
|
|
|
+
|
|
|
+ var commonInfo: LNVAPCommonInfo? {
|
|
|
+ didSet {
|
|
|
+ updateMainVertexBuffer()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var maskInfo: LNVAPMaskInfo? {
|
|
|
+ didSet {
|
|
|
+ updateMainVertexBuffer()
|
|
|
+ updateMaskTexture()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ init?(metalLayer: CAMetalLayer) {
|
|
|
+ guard let device = MTLCreateSystemDefaultDevice(),
|
|
|
+ let queue = device.makeCommandQueue() else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ self.device = device
|
|
|
+ self.pixelFormat = metalLayer.pixelFormat
|
|
|
+ self.commandQueue = queue
|
|
|
+ self.shaderLoader = LNVAPMetalShaderFunctionLoader(device: device)
|
|
|
+
|
|
|
+ metalLayer.device = device
|
|
|
+ metalLayer.framebufferOnly = false
|
|
|
+
|
|
|
+ let cacheStatus = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache)
|
|
|
+ guard cacheStatus == kCVReturnSuccess else { return nil }
|
|
|
+
|
|
|
+ updateYuvMatrixBuffer(matrix: Self.matrix601Full)
|
|
|
+ }
|
|
|
+
|
|
|
+ func render(pixelBuffer: CVPixelBuffer, metalLayer: CAMetalLayer, mergeInfos: [LNVAPMergedInfo]) {
|
|
|
+ guard metalLayer.bounds.width > 0,
|
|
|
+ metalLayer.bounds.height > 0,
|
|
|
+ let drawable = metalLayer.nextDrawable(),
|
|
|
+ let commandBuffer = commandQueue.makeCommandBuffer(),
|
|
|
+ let descriptor = currentRenderPassDescriptor(texture: drawable.texture),
|
|
|
+ let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ updateYuvMatrixIfNeeded(pixelBuffer: pixelBuffer)
|
|
|
+ guard let yTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 0, format: .r8Unorm),
|
|
|
+ let uvTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 1, format: .rg8Unorm) else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ drawBackground(encoder: encoder, yTexture: yTexture, uvTexture: uvTexture)
|
|
|
+ drawMergedAttachments(encoder: encoder, yTexture: yTexture, uvTexture: uvTexture, mergeInfos: mergeInfos)
|
|
|
+
|
|
|
+ encoder.endEncoding()
|
|
|
+ commandBuffer.present(drawable)
|
|
|
+ commandBuffer.commit()
|
|
|
+ }
|
|
|
+
|
|
|
+ func dispose() {
|
|
|
+ if let textureCache {
|
|
|
+ CVMetalTextureCacheFlush(textureCache, 0)
|
|
|
+ }
|
|
|
+ textureCache = nil
|
|
|
+ vertexBuffer = nil
|
|
|
+ yuvMatrixBuffer = nil
|
|
|
+ maskBlurBuffer = nil
|
|
|
+ maskTexture = nil
|
|
|
+ defaultMainPipelineState = nil
|
|
|
+ mainPipelineStateForMask = nil
|
|
|
+ mainPipelineStateForMaskBlur = nil
|
|
|
+ attachmentPipelineState = nil
|
|
|
+ }
|
|
|
+
|
|
|
+ private func drawBackground(encoder: MTLRenderCommandEncoder, yTexture: MTLTexture, uvTexture: MTLTexture) {
|
|
|
+ guard let vertexBuffer,
|
|
|
+ let yuvMatrixBuffer else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if let maskInfo,
|
|
|
+ let maskTexture {
|
|
|
+ if maskInfo.blurLength > 0 {
|
|
|
+ guard let pipelineState = mainPipelineStateForMaskBlur ?? makeMaskBlurPipelineState(),
|
|
|
+ let maskBlurBuffer = maskBlurBuffer ?? makeMaskBlurBuffer() else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ encoder.setRenderPipelineState(pipelineState)
|
|
|
+ encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentBuffer(maskBlurBuffer, offset: 0, index: 1)
|
|
|
+ encoder.setFragmentTexture(yTexture, index: 0)
|
|
|
+ encoder.setFragmentTexture(uvTexture, index: 1)
|
|
|
+ encoder.setFragmentTexture(maskTexture, index: 2)
|
|
|
+ encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let pipelineState = mainPipelineStateForMask ?? makeMaskPipelineState() else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ encoder.setRenderPipelineState(pipelineState)
|
|
|
+ encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentTexture(yTexture, index: 0)
|
|
|
+ encoder.setFragmentTexture(uvTexture, index: 1)
|
|
|
+ encoder.setFragmentTexture(maskTexture, index: 2)
|
|
|
+ encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let pipelineState = defaultMainPipelineState ?? makeMainPipelineState() else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ encoder.setRenderPipelineState(pipelineState)
|
|
|
+ encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentTexture(yTexture, index: 0)
|
|
|
+ encoder.setFragmentTexture(uvTexture, index: 1)
|
|
|
+ encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func drawMergedAttachments(encoder: MTLRenderCommandEncoder, yTexture: MTLTexture, uvTexture: MTLTexture, mergeInfos: [LNVAPMergedInfo]) {
|
|
|
+ guard !mergeInfos.isEmpty,
|
|
|
+ let commonInfo,
|
|
|
+ let pipelineState = attachmentPipelineState ?? makeAttachmentPipelineState(),
|
|
|
+ let yuvMatrixBuffer else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for mergeInfo in mergeInfos {
|
|
|
+ guard let sourceTexture = mergeInfo.source?.texture,
|
|
|
+ let colorParamsBuffer = mergeInfo.source?.colorParamsBuffer,
|
|
|
+ let mergeVertexBuffer = mergeInfo.vertexBuffer(containerSize: commonInfo.size,
|
|
|
+ maskContianerSize: commonInfo.videoSize,
|
|
|
+ device: device) else {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ encoder.setRenderPipelineState(pipelineState)
|
|
|
+ encoder.setVertexBuffer(mergeVertexBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
|
|
|
+ encoder.setFragmentBuffer(colorParamsBuffer, offset: 0, index: 1)
|
|
|
+ encoder.setFragmentTexture(yTexture, index: 0)
|
|
|
+ encoder.setFragmentTexture(uvTexture, index: 1)
|
|
|
+ encoder.setFragmentTexture(sourceTexture, index: 2)
|
|
|
+ encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeMainPipelineState() -> MTLRenderPipelineState? {
|
|
|
+ guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapVertexFunctionName,
|
|
|
+ fragmentFunction: LNVAPMetalUtil.vapYUVFragmentFunctionName) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ defaultMainPipelineState = state
|
|
|
+ return state
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeMaskPipelineState() -> MTLRenderPipelineState? {
|
|
|
+ guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapVertexFunctionName,
|
|
|
+ fragmentFunction: LNVAPMetalUtil.vapMaskFragmentFunctionName) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ mainPipelineStateForMask = state
|
|
|
+ return state
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeMaskBlurPipelineState() -> MTLRenderPipelineState? {
|
|
|
+ guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapVertexFunctionName,
|
|
|
+ fragmentFunction: LNVAPMetalUtil.vapMaskBlurFragmentFunctionName) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ mainPipelineStateForMaskBlur = state
|
|
|
+ return state
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeAttachmentPipelineState() -> MTLRenderPipelineState? {
|
|
|
+ guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapAttachmentVertexFunctionName,
|
|
|
+ fragmentFunction: LNVAPMetalUtil.vapAttachmentFragmentFunctionName) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ attachmentPipelineState = state
|
|
|
+ return state
|
|
|
+ }
|
|
|
+
|
|
|
+ private func createPipelineState(vertexFunction: String, fragmentFunction: String) -> MTLRenderPipelineState? {
|
|
|
+ guard let vertexProgram = shaderLoader.loadFunction(withName: vertexFunction),
|
|
|
+ let fragmentProgram = shaderLoader.loadFunction(withName: fragmentFunction) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ let descriptor = MTLRenderPipelineDescriptor()
|
|
|
+ descriptor.vertexFunction = vertexProgram
|
|
|
+ descriptor.fragmentFunction = fragmentProgram
|
|
|
+ descriptor.colorAttachments[0].pixelFormat = pixelFormat
|
|
|
+ descriptor.colorAttachments[0].isBlendingEnabled = true
|
|
|
+ descriptor.colorAttachments[0].rgbBlendOperation = .add
|
|
|
+ descriptor.colorAttachments[0].alphaBlendOperation = .add
|
|
|
+ descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
|
|
|
+ descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
|
|
|
+ descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
|
|
|
+ descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
|
|
|
+ return try? device.makeRenderPipelineState(descriptor: descriptor)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateMainVertexBuffer() {
|
|
|
+ guard let commonInfo else {
|
|
|
+ vertexBuffer = nil
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let vertices = LNVAPMetalUtil.vapMTLVerticesIdentity
|
|
|
+ let rgbCoordinates = LNVAPMetalUtil.genTextureCoordinates(rect: commonInfo.rgbAreaRect, containerSize: commonInfo.videoSize, reverse: false, degree: 0)
|
|
|
+ let alphaCoordinates = LNVAPMetalUtil.genTextureCoordinates(rect: commonInfo.alphaAreaRect, containerSize: commonInfo.videoSize, reverse: false, degree: 0)
|
|
|
+ let maskCoordinates: [Float]
|
|
|
+ if let maskInfo {
|
|
|
+ maskCoordinates = LNVAPMetalUtil.genTextureCoordinates(rect: maskInfo.sampleRect, containerSize: maskInfo.dataSize, reverse: false, degree: 0)
|
|
|
+ } else {
|
|
|
+ maskCoordinates = [Float](repeating: 0, count: 8)
|
|
|
+ }
|
|
|
+
|
|
|
+ var vertexData = [Float](repeating: 0, count: 40)
|
|
|
+ var index = 0
|
|
|
+ for i in 0..<16 {
|
|
|
+ vertexData[index] = vertices[i]
|
|
|
+ index += 1
|
|
|
+ if i % 4 == 3 {
|
|
|
+ let row = i / 4
|
|
|
+ vertexData[index] = rgbCoordinates[row * 2]
|
|
|
+ index += 1
|
|
|
+ vertexData[index] = rgbCoordinates[row * 2 + 1]
|
|
|
+ index += 1
|
|
|
+ vertexData[index] = alphaCoordinates[row * 2]
|
|
|
+ index += 1
|
|
|
+ vertexData[index] = alphaCoordinates[row * 2 + 1]
|
|
|
+ index += 1
|
|
|
+ vertexData[index] = maskCoordinates[row * 2]
|
|
|
+ index += 1
|
|
|
+ vertexData[index] = maskCoordinates[row * 2 + 1]
|
|
|
+ index += 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ vertexBuffer = device.makeBuffer(bytes: vertexData, length: vertexData.count * MemoryLayout<Float>.size, options: .storageModeShared)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateMaskTexture() {
|
|
|
+ guard let maskInfo else {
|
|
|
+ maskTexture = nil
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let width = Int(maskInfo.dataSize.width)
|
|
|
+ let height = Int(maskInfo.dataSize.height)
|
|
|
+ guard width > 0,
|
|
|
+ height > 0,
|
|
|
+ !maskInfo.data.isEmpty else {
|
|
|
+ maskTexture = nil
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .r8Unorm, width: width, height: height, mipmapped: false)
|
|
|
+ descriptor.usage = [.shaderRead]
|
|
|
+ descriptor.storageMode = .shared
|
|
|
+ guard let texture = device.makeTexture(descriptor: descriptor) else {
|
|
|
+ maskTexture = nil
|
|
|
+ return
|
|
|
+ }
|
|
|
+ maskInfo.data.withUnsafeBytes { bytes in
|
|
|
+ guard let base = bytes.baseAddress else { return }
|
|
|
+ texture.replace(region: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0, withBytes: base, bytesPerRow: width)
|
|
|
+ }
|
|
|
+ maskTexture = texture
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeMaskBlurBuffer() -> MTLBuffer? {
|
|
|
+ let weight = simd_float3x3(
|
|
|
+ SIMD3<Float>(0.0625, 0.125, 0.0625),
|
|
|
+ SIMD3<Float>(0.125, 0.25, 0.125),
|
|
|
+ SIMD3<Float>(0.0625, 0.125, 0.0625)
|
|
|
+ )
|
|
|
+ let params = LNMaskParameters(weightMatrix: weight, coreSize: 3, texelOffset: 0.01)
|
|
|
+ let buffer = device.makeBuffer(bytes: [params], length: MemoryLayout<LNMaskParameters>.stride, options: .storageModeShared)
|
|
|
+ maskBlurBuffer = buffer
|
|
|
+ return buffer
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeTexture(pixelBuffer: CVPixelBuffer, plane: Int, format: MTLPixelFormat) -> MTLTexture? {
|
|
|
+ guard let textureCache else { return nil }
|
|
|
+ let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, plane)
|
|
|
+ let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane)
|
|
|
+ var cvTexture: CVMetalTexture?
|
|
|
+ let status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
|
|
|
+ textureCache,
|
|
|
+ pixelBuffer,
|
|
|
+ nil,
|
|
|
+ format,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ plane,
|
|
|
+ &cvTexture)
|
|
|
+ guard status == kCVReturnSuccess, let cvTexture else { return nil }
|
|
|
+ return CVMetalTextureGetTexture(cvTexture)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func currentRenderPassDescriptor(texture: MTLTexture) -> MTLRenderPassDescriptor? {
|
|
|
+ let descriptor = MTLRenderPassDescriptor()
|
|
|
+ descriptor.colorAttachments[0].texture = texture
|
|
|
+ descriptor.colorAttachments[0].loadAction = .clear
|
|
|
+ descriptor.colorAttachments[0].storeAction = .store
|
|
|
+ descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
|
|
|
+ return descriptor
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateYuvMatrixIfNeeded(pixelBuffer: CVPixelBuffer) {
|
|
|
+ let matrixAttachment = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, nil)?.takeUnretainedValue()
|
|
|
+ let matrix = (matrixAttachment != nil && CFEqual(matrixAttachment, kCVImageBufferYCbCrMatrix_ITU_R_709_2)) ? Self.matrix709Full : Self.matrix601Full
|
|
|
+ guard matrix != currentColorMatrix else { return }
|
|
|
+ currentColorMatrix = matrix
|
|
|
+ updateYuvMatrixBuffer(matrix: matrix)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateYuvMatrixBuffer(matrix: simd_float3x3) {
|
|
|
+ let params = LNColorParameters(matrix: matrix, offset: simd_float2(0.5, 0.5))
|
|
|
+ yuvMatrixBuffer = device.makeBuffer(bytes: [params], length: MemoryLayout<LNColorParameters>.stride, options: .storageModeShared)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private final class LNMetalUnavailableBridge: NSObject {
|
|
|
+ var callback: (() -> Void)?
|
|
|
+
|
|
|
+ @objc func onMetalViewUnavailable() {
|
|
|
+ callback?()
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc func onViewUnavailableStatus() {
|
|
|
+ callback?()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private enum LNVAPLegacyMapper {
|
|
|
+ static func toLegacyCommonInfo(_ info: LNVAPCommonInfo?) -> NSObject? {
|
|
|
+ guard let info, let obj = LNLegacyRuntime.instantiate("QGVAPCommonInfo") else { return nil }
|
|
|
+ LNLegacyRuntime.set(obj, "version", info.version)
|
|
|
+ LNLegacyRuntime.set(obj, "framesCount", info.framesCount)
|
|
|
+ LNLegacyRuntime.set(obj, "size", NSValue(cgSize: info.size))
|
|
|
+ LNLegacyRuntime.set(obj, "videoSize", NSValue(cgSize: info.videoSize))
|
|
|
+ LNLegacyRuntime.set(obj, "targetOrientaion", info.targetOrientaion.rawValue)
|
|
|
+ LNLegacyRuntime.set(obj, "fps", info.fps)
|
|
|
+ LNLegacyRuntime.set(obj, "isMerged", info.isMerged)
|
|
|
+ LNLegacyRuntime.set(obj, "alphaAreaRect", NSValue(cgRect: info.alphaAreaRect))
|
|
|
+ LNLegacyRuntime.set(obj, "rgbAreaRect", NSValue(cgRect: info.rgbAreaRect))
|
|
|
+ return obj
|
|
|
+ }
|
|
|
+
|
|
|
+ static func toLegacyMaskInfo(_ info: LNVAPMaskInfo?) -> NSObject? {
|
|
|
+ guard let info, let obj = LNLegacyRuntime.instantiate("QGVAPMaskInfo") else { return nil }
|
|
|
+ LNLegacyRuntime.set(obj, "data", info.data)
|
|
|
+ LNLegacyRuntime.set(obj, "dataSize", NSValue(cgSize: info.dataSize))
|
|
|
+ LNLegacyRuntime.set(obj, "positionInVideoRect", NSValue(cgRect: info.sampleRect))
|
|
|
+ return obj
|
|
|
+ }
|
|
|
+
|
|
|
+ static func toLegacySourceInfo(_ info: LNVAPSourceInfo?) -> NSObject? {
|
|
|
+ guard let info, let obj = LNLegacyRuntime.instantiate("QGVAPSourceInfo") else { return nil }
|
|
|
+ LNLegacyRuntime.set(obj, "contentTag", info.contentTag)
|
|
|
+ LNLegacyRuntime.set(obj, "contentTagValue", info.contentTagValue)
|
|
|
+ LNLegacyRuntime.set(obj, "color", info.color)
|
|
|
+ LNLegacyRuntime.set(obj, "size", NSValue(cgSize: info.size))
|
|
|
+ LNLegacyRuntime.set(obj, "sourceImage", info.sourceImage)
|
|
|
+ LNLegacyRuntime.set(obj, "texture", info.texture)
|
|
|
+ LNLegacyRuntime.set(obj, "colorParamsBuffer", info.colorParamsBuffer)
|
|
|
+ return obj
|
|
|
+ }
|
|
|
+
|
|
|
+ static func toLegacyMergedInfos(_ infos: [LNVAPMergedInfo]) -> NSArray {
|
|
|
+ let out = NSMutableArray(capacity: infos.count)
|
|
|
+ for info in infos {
|
|
|
+ guard let obj = LNLegacyRuntime.instantiate("QGVAPMergedInfo") else { continue }
|
|
|
+ LNLegacyRuntime.set(obj, "source", toLegacySourceInfo(info.source))
|
|
|
+ LNLegacyRuntime.set(obj, "renderIndex", info.renderIndex)
|
|
|
+ LNLegacyRuntime.set(obj, "renderRect", NSValue(cgRect: info.renderRect))
|
|
|
+ LNLegacyRuntime.set(obj, "needMask", info.needMask)
|
|
|
+ LNLegacyRuntime.set(obj, "maskRect", NSValue(cgRect: info.maskRect))
|
|
|
+ LNLegacyRuntime.set(obj, "maskRotation", info.maskRotation)
|
|
|
+ out.add(obj)
|
|
|
+ }
|
|
|
+ return out
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@objcMembers
|
|
|
+public final class LNHWDMetalRenderer: NSObject {
|
|
|
+ public var blendMode: QGHWDTextureBlendMode {
|
|
|
+ didSet {
|
|
|
+ if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNHWDMetalCoreRenderer {
|
|
|
+ swiftRenderer.updateBlendMode(blendMode)
|
|
|
+ }
|
|
|
+ if let legacy = legacyRenderer {
|
|
|
+ LNLegacyRuntime.set(legacy, "blendMode", blendMode.rawValue)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private let fallback = LNPixelBufferLayerRenderer()
|
|
|
+ private let fallbackLayer = CALayer()
|
|
|
+ private let legacyRenderer: NSObject?
|
|
|
+ private let swiftRenderer: AnyObject?
|
|
|
+
|
|
|
+ @objc(initWithMetalLayer:blendMode:)
|
|
|
+ public init(metalLayer: AnyObject, blendMode: QGHWDTextureBlendMode) {
|
|
|
+ self.blendMode = blendMode
|
|
|
+ if #available(iOS 13.0, *), let metalLayer = metalLayer as? CAMetalLayer {
|
|
|
+ self.swiftRenderer = LNHWDMetalCoreRenderer(metalLayer: metalLayer, blendMode: blendMode)
|
|
|
+ } else {
|
|
|
+ self.swiftRenderer = nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if self.swiftRenderer == nil, let rendererObj = LNLegacyRuntime.instantiate("QGHWDMetalRenderer") {
|
|
|
+ let created = LNLegacyRuntime.callInitWithMetalLayerBlend(rendererObj, layer: metalLayer, blend: Int(blendMode.rawValue))
|
|
|
+ self.legacyRenderer = (created as? NSObject) ?? rendererObj
|
|
|
+ } else {
|
|
|
+ self.legacyRenderer = nil
|
|
|
+ }
|
|
|
+ super.init()
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(renderPixelBuffer:metalLayer:)
|
|
|
+ public func renderPixelBuffer(_ pixelBuffer: CVPixelBuffer?, metalLayer: AnyObject) {
|
|
|
+ if #available(iOS 13.0, *),
|
|
|
+ let pixelBuffer,
|
|
|
+ let metalLayer = metalLayer as? CAMetalLayer,
|
|
|
+ let swiftRenderer = swiftRenderer as? LNHWDMetalCoreRenderer {
|
|
|
+ swiftRenderer.render(pixelBuffer: pixelBuffer, metalLayer: metalLayer)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if let legacy = legacyRenderer {
|
|
|
+ LNLegacyRuntime.callRenderPixelBuffer(legacy, pixelBuffer: pixelBuffer, layer: metalLayer)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ guard let pixelBuffer else { return }
|
|
|
+ let target = (metalLayer as? CALayer) ?? fallbackLayer
|
|
|
+ fallback.render(pixelBuffer: pixelBuffer, into: target)
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(dispose)
|
|
|
+ public func dispose() {
|
|
|
+ if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNHWDMetalCoreRenderer {
|
|
|
+ swiftRenderer.dispose()
|
|
|
+ }
|
|
|
+ if let legacy = legacyRenderer {
|
|
|
+ LNLegacyRuntime.callNoArgs(legacy, "dispose")
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@objcMembers
|
|
|
+public final class LNHWDMetalView: UIView {
|
|
|
+ public weak var delegate: LNHWDMetalViewDelegate?
|
|
|
+ public weak var lnDelegate: LNHWDMetalViewDelegate? {
|
|
|
+ get { delegate }
|
|
|
+ set { delegate = newValue }
|
|
|
+ }
|
|
|
+ public var blendMode: QGHWDTextureBlendMode {
|
|
|
+ didSet {
|
|
|
+ fallbackRenderer.blendMode = blendMode
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private let unavailableBridge = LNMetalUnavailableBridge()
|
|
|
+ private var renderLayer: CALayer = CALayer()
|
|
|
+ private lazy var fallbackRenderer = LNHWDMetalRenderer(metalLayer: renderLayer, blendMode: blendMode)
|
|
|
+
|
|
|
+ @objc(initWithFrame:blendMode:)
|
|
|
+ public init(frame: CGRect, blendMode: QGHWDTextureBlendMode) {
|
|
|
+ self.blendMode = blendMode
|
|
|
+ super.init(frame: frame)
|
|
|
+ setupRenderingLayer(frame: frame)
|
|
|
+ }
|
|
|
+
|
|
|
+ public required init?(coder: NSCoder) {
|
|
|
+ self.blendMode = .alphaLeft
|
|
|
+ super.init(coder: coder)
|
|
|
+ setupRenderingLayer(frame: bounds)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func setupRenderingLayer(frame: CGRect) {
|
|
|
+ unavailableBridge.callback = { [weak self] in
|
|
|
+ self?.delegate?.onMetalViewUnavailable()
|
|
|
+ }
|
|
|
+
|
|
|
+ if #available(iOS 13.0, *) {
|
|
|
+ let metalLayer = CAMetalLayer()
|
|
|
+ metalLayer.pixelFormat = .bgra8Unorm
|
|
|
+ renderLayer = metalLayer
|
|
|
+ } else {
|
|
|
+ renderLayer = CALayer()
|
|
|
+ }
|
|
|
+
|
|
|
+ renderLayer.frame = frame
|
|
|
+ renderLayer.contentsScale = UIScreen.main.scale
|
|
|
+ renderLayer.contentsGravity = .resizeAspect
|
|
|
+ layer.addSublayer(renderLayer)
|
|
|
+ }
|
|
|
+
|
|
|
+ public override func layoutSubviews() {
|
|
|
+ super.layoutSubviews()
|
|
|
+ renderLayer.frame = bounds
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(display:)
|
|
|
+ public func display(_ pixelBuffer: CVPixelBuffer?) {
|
|
|
+ fallbackRenderer.renderPixelBuffer(pixelBuffer, metalLayer: renderLayer)
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(dispose)
|
|
|
+ public func dispose() {
|
|
|
+ renderLayer.contents = nil
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@objcMembers
|
|
|
+public final class LNVAPMetalRenderer: NSObject {
|
|
|
+ public var commonInfo: LNVAPCommonInfo? {
|
|
|
+ didSet {
|
|
|
+ if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
|
|
|
+ swiftRenderer.commonInfo = commonInfo
|
|
|
+ }
|
|
|
+ guard let legacy = legacyRenderer else { return }
|
|
|
+ LNLegacyRuntime.set(legacy, "commonInfo", LNVAPLegacyMapper.toLegacyCommonInfo(commonInfo))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public var maskInfo: LNVAPMaskInfo? {
|
|
|
+ didSet {
|
|
|
+ if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
|
|
|
+ swiftRenderer.maskInfo = maskInfo
|
|
|
+ }
|
|
|
+ guard let legacy = legacyRenderer else { return }
|
|
|
+ LNLegacyRuntime.set(legacy, "maskInfo", LNVAPLegacyMapper.toLegacyMaskInfo(maskInfo))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private let fallback = LNPixelBufferLayerRenderer()
|
|
|
+ private let fallbackLayer = CALayer()
|
|
|
+ private let legacyRenderer: NSObject?
|
|
|
+ private let swiftRenderer: AnyObject?
|
|
|
+
|
|
|
+ @objc(initWithMetalLayer:)
|
|
|
+ public init(metalLayer: AnyObject) {
|
|
|
+ if #available(iOS 13.0, *), let metalLayer = metalLayer as? CAMetalLayer {
|
|
|
+ self.swiftRenderer = LNVAPMetalCoreRenderer(metalLayer: metalLayer)
|
|
|
+ } else {
|
|
|
+ self.swiftRenderer = nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if let rendererObj = LNLegacyRuntime.instantiate("QGVAPMetalRenderer") {
|
|
|
+ let created = LNLegacyRuntime.callInitWithMetalLayer(rendererObj, layer: metalLayer)
|
|
|
+ self.legacyRenderer = (created as? NSObject) ?? rendererObj
|
|
|
+ } else {
|
|
|
+ self.legacyRenderer = nil
|
|
|
+ }
|
|
|
+ super.init()
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(renderPixelBuffer:metalLayer:mergeInfos:)
|
|
|
+ public func renderPixelBuffer(_ pixelBuffer: CVPixelBuffer?, metalLayer: AnyObject, mergeInfos: [LNVAPMergedInfo]) {
|
|
|
+ if #available(iOS 13.0, *),
|
|
|
+ let pixelBuffer,
|
|
|
+ let metalLayer = metalLayer as? CAMetalLayer,
|
|
|
+ let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
|
|
|
+ swiftRenderer.render(pixelBuffer: pixelBuffer, metalLayer: metalLayer, mergeInfos: mergeInfos)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if let legacy = legacyRenderer {
|
|
|
+ LNLegacyRuntime.set(legacy, "commonInfo", LNVAPLegacyMapper.toLegacyCommonInfo(commonInfo))
|
|
|
+ LNLegacyRuntime.set(legacy, "maskInfo", LNVAPLegacyMapper.toLegacyMaskInfo(maskInfo))
|
|
|
+ LNLegacyRuntime.callRenderPixelBufferMerge(legacy,
|
|
|
+ pixelBuffer: pixelBuffer,
|
|
|
+ layer: metalLayer,
|
|
|
+ infos: LNVAPLegacyMapper.toLegacyMergedInfos(mergeInfos))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let pixelBuffer else { return }
|
|
|
+ let target = (metalLayer as? CALayer) ?? fallbackLayer
|
|
|
+ fallback.render(pixelBuffer: pixelBuffer, into: target)
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(dispose)
|
|
|
+ public func dispose() {
|
|
|
+ if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
|
|
|
+ swiftRenderer.dispose()
|
|
|
+ }
|
|
|
+ if let legacy = legacyRenderer {
|
|
|
+ LNLegacyRuntime.callNoArgs(legacy, "dispose")
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@objcMembers
|
|
|
+public final class LNVAPMetalView: UIView {
|
|
|
+ public weak var delegate: LNVAPMetalViewDelegate?
|
|
|
+ public weak var lnDelegate: LNVAPMetalViewDelegate? {
|
|
|
+ get { delegate }
|
|
|
+ set { delegate = newValue }
|
|
|
+ }
|
|
|
+ public var commonInfo: LNVAPCommonInfo?
|
|
|
+ public var maskInfo: LNVAPMaskInfo?
|
|
|
+
|
|
|
+ private let unavailableBridge = LNMetalUnavailableBridge()
|
|
|
+ private var renderLayer: CALayer = CALayer()
|
|
|
+ private lazy var fallbackRenderer = LNVAPMetalRenderer(metalLayer: renderLayer)
|
|
|
+
|
|
|
+ public override init(frame: CGRect) {
|
|
|
+ super.init(frame: frame)
|
|
|
+ setupRenderingLayer(frame: frame)
|
|
|
+ }
|
|
|
+
|
|
|
+ public required init?(coder: NSCoder) {
|
|
|
+ super.init(coder: coder)
|
|
|
+ setupRenderingLayer(frame: bounds)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func setupRenderingLayer(frame: CGRect) {
|
|
|
+ unavailableBridge.callback = { [weak self] in
|
|
|
+ self?.delegate?.onMetalViewUnavailable()
|
|
|
+ }
|
|
|
+
|
|
|
+ if #available(iOS 13.0, *) {
|
|
|
+ let metalLayer = CAMetalLayer()
|
|
|
+ metalLayer.pixelFormat = .bgra8Unorm
|
|
|
+ renderLayer = metalLayer
|
|
|
+ } else {
|
|
|
+ renderLayer = CALayer()
|
|
|
+ }
|
|
|
+
|
|
|
+ renderLayer.frame = frame
|
|
|
+ renderLayer.contentsScale = UIScreen.main.scale
|
|
|
+ renderLayer.contentsGravity = .resizeAspect
|
|
|
+ layer.addSublayer(renderLayer)
|
|
|
+ }
|
|
|
+
|
|
|
+ public override func layoutSubviews() {
|
|
|
+ super.layoutSubviews()
|
|
|
+ renderLayer.frame = bounds
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(display:mergeInfos:)
|
|
|
+ public func display(_ pixelBuffer: CVPixelBuffer?, mergeInfos: [LNVAPMergedInfo]) {
|
|
|
+ fallbackRenderer.commonInfo = commonInfo
|
|
|
+ fallbackRenderer.maskInfo = maskInfo
|
|
|
+ fallbackRenderer.renderPixelBuffer(pixelBuffer, metalLayer: renderLayer, mergeInfos: mergeInfos)
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(dispose)
|
|
|
+ public func dispose() {
|
|
|
+ renderLayer.contents = nil
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@objcMembers
|
|
|
+public final class LNHWDMP4OpenGLView: UIView {
|
|
|
+ public weak var displayDelegate: LNHWDMP4OpenGLViewDelegate?
|
|
|
+ public var glContext: AnyObject?
|
|
|
+
|
|
|
+ public var blendMode: QGHWDTextureBlendMode = .alphaLeft {
|
|
|
+ didSet {
|
|
|
+ fallbackRenderer.blendMode = blendMode
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public var pause: Bool = false
|
|
|
+ private var renderLayer: CALayer = CALayer()
|
|
|
+ private lazy var fallbackRenderer = LNHWDMetalRenderer(metalLayer: renderLayer, blendMode: blendMode)
|
|
|
+
|
|
|
+ public override init(frame: CGRect) {
|
|
|
+ super.init(frame: frame)
|
|
|
+ setupRenderingLayer(frame: frame)
|
|
|
+ }
|
|
|
+
|
|
|
+ public required init?(coder: NSCoder) {
|
|
|
+ super.init(coder: coder)
|
|
|
+ setupRenderingLayer(frame: bounds)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func setupRenderingLayer(frame: CGRect) {
|
|
|
+ if #available(iOS 13.0, *) {
|
|
|
+ let metalLayer = CAMetalLayer()
|
|
|
+ metalLayer.pixelFormat = .bgra8Unorm
|
|
|
+ renderLayer = metalLayer
|
|
|
+ } else {
|
|
|
+ renderLayer = CALayer()
|
|
|
+ }
|
|
|
+
|
|
|
+ renderLayer.frame = frame
|
|
|
+ renderLayer.contentsScale = UIScreen.main.scale
|
|
|
+ renderLayer.contentsGravity = .resizeAspect
|
|
|
+ layer.addSublayer(renderLayer)
|
|
|
+ }
|
|
|
+
|
|
|
+ public override func layoutSubviews() {
|
|
|
+ super.layoutSubviews()
|
|
|
+ renderLayer.frame = bounds
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(setupGL)
|
|
|
+ public func setupGL() {
|
|
|
+ renderLayer.frame = bounds
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(displayPixelBuffer:)
|
|
|
+ public func displayPixelBuffer(_ pixelBuffer: CVPixelBuffer?) {
|
|
|
+ if window == nil {
|
|
|
+ displayDelegate?.onViewUnavailableStatus()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ guard !pause, let pixelBuffer else { return }
|
|
|
+ fallbackRenderer.blendMode = blendMode
|
|
|
+ fallbackRenderer.renderPixelBuffer(pixelBuffer, metalLayer: renderLayer)
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(dispose)
|
|
|
+ public func dispose() {
|
|
|
+ fallbackRenderer.dispose()
|
|
|
+ renderLayer.contents = nil
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc(updateBackingSize)
|
|
|
+ public func updateBackingSize() {
|
|
|
+ renderLayer.frame = bounds
|
|
|
+ }
|
|
|
+}
|