VPCamera3/tdvideo/tdvideo/MetalPlayer.swift
2024-03-05 11:44:34 +08:00

346 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MetalPlayer.swift
// tdvideo
//
// Created by aaa on 2024/1/19.
//
import AVFoundation
import MetalKit
import SwiftUI
/// Metal
class MetalPlayer: MTKView {
// MARK: - Properties
// MARK: Private
///
private let colorSpace = CGColorSpaceCreateDeviceRGB()
///
private lazy var commandQueue: MTLCommandQueue? = {
return self.device!.makeCommandQueue()
}()
/// CIContext
private lazy var context: CIContext = {
return CIContext(
mtlDevice: self.device!,
options: [CIContextOption.workingColorSpace : NSNull()]
)
}()
/// 使
private let metalDevice = MTLCreateSystemDefaultDevice()!
///
private var textureCache: CVMetalTextureCache?
/// ' CVPixelBuffer '
private var outputPixelBufferPool: CVPixelBufferPool?
///
private var computePipelineState: MTLComputePipelineState?
///
private var image: CIImage? {
didSet {
draw()
}
}
// MARK: - Methods
// MARK: Public
///使
/// -frameect:
init(frame frameRect: CGRect) {
super.init(
frame: frameRect,
device: metalDevice
)
setup(frameSize: frameRect.size)
}
///
/// -aDecoder:NSCoder
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
device = MTLCreateSystemDefaultDevice()
setup(frameSize: .zero)
}
// MARK: Private
///
/// -frameSize:
private func setup(frameSize: CGSize) {
framebufferOnly = false
enableSetNeedsDisplay = false
guard let defaultLibrary = metalDevice.makeDefaultLibrary() else {
assertionFailure("Could not create default Metal device.")
return
}
let kernelFunction = defaultLibrary.makeFunction(name: "sideBySideEffect")
do {
computePipelineState = try metalDevice.makeComputePipelineState(function: kernelFunction!)
} catch {
print("Could not create pipeline state: \(error)")
}
setupCache(
outputRetainedBufferCountHint: 5,
frameSize: frameSize
)
}
///
/// -:
/// - outputRetainedBufferCountHint:
/// - frameSize:
private func setupCache(
outputRetainedBufferCountHint: Int,
frameSize: CGSize
) {
reset()
let outputSize = CGSize(
width: frameSize.width,
height: frameSize.height
)
guard let outputPixelBufferPool = createBufferPool(size: outputSize) else {return}
self.outputPixelBufferPool = outputPixelBufferPool
var metalTextureCache: CVMetalTextureCache?
if CVMetalTextureCacheCreate(
kCFAllocatorDefault,
nil,
metalDevice,
nil,
&metalTextureCache
) != kCVReturnSuccess {
assertionFailure("Unable to allocate texture cache")
} else {
textureCache = metalTextureCache
}
}
///便
func reset() {
outputPixelBufferPool = nil
textureCache = nil
}
///' CVPixelBuffer '
/// -:
/// - leftPixelBuffer:
/// - rightPixelBuffer:
func render(
leftPixelBuffer: CVPixelBuffer,
rightPixelBuffer: CVPixelBuffer
) {
var outputPixelBuffer: CVPixelBuffer?
CVPixelBufferPoolCreatePixelBuffer(
kCFAllocatorDefault,
outputPixelBufferPool!,
&outputPixelBuffer
)
guard let outputBuffer = outputPixelBuffer else {
print("Allocation failure: Could not get pixel buffer from pool. (\(self.description))")
return
}
guard
let leftInputTexture = makeTextureFromCVPixelBuffer(
pixelBuffer: leftPixelBuffer,
textureFormat: .bgra8Unorm
),
let rightInputTexture = makeTextureFromCVPixelBuffer(
pixelBuffer: rightPixelBuffer,
textureFormat: .bgra8Unorm
),
let outputTexture = makeTextureFromCVPixelBuffer(
pixelBuffer: outputBuffer,
textureFormat: .bgra8Unorm
)
else { return }
//
guard let commandQueue = commandQueue,
let commandBuffer = commandQueue.makeCommandBuffer(),
let commandEncoder = commandBuffer.makeComputeCommandEncoder() else {
print("Failed to create a Metal command queue.")
CVMetalTextureCacheFlush(textureCache!, 0)
return
}
commandEncoder.label = "BlendGPU"
commandEncoder.setComputePipelineState(computePipelineState!)
commandEncoder.setTexture(leftInputTexture, index: 0)
commandEncoder.setTexture(rightInputTexture, index: 1)
commandEncoder.setTexture(outputTexture, index: 2)
//线
let width = computePipelineState!.threadExecutionWidth
let height = computePipelineState!.maxTotalThreadsPerThreadgroup / width
let threadsPerThreadgroup = MTLSizeMake(width, height, 1)
let threadgroupsPerGrid = MTLSize(width: (leftInputTexture.width + width - 1) / width,
height: (leftInputTexture.height + height - 1) / height,
depth: 1)
commandEncoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup)
commandEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
guard let outputPixelBuffer else { return }
self.image = CIImage(cvPixelBuffer: outputPixelBuffer)
}
///' CVPixelBuffer '
/// -:
/// - leftPixelBuffer:
/// - rightPixelBuffer:
func render1(
pixelBuffer: CVPixelBuffer
) {
var outputPixelBuffer: CVPixelBuffer?
CVPixelBufferPoolCreatePixelBuffer(
kCFAllocatorDefault,
outputPixelBufferPool!,
&outputPixelBuffer
)
guard let outputBuffer = outputPixelBuffer else {
print("Allocation failure: Could not get pixel buffer from pool. (\(self.description))")
return
}
guard
let inputTexture = makeTextureFromCVPixelBuffer(
pixelBuffer: pixelBuffer,
textureFormat: .bgra8Unorm
),
let outputTexture = makeTextureFromCVPixelBuffer(
pixelBuffer: outputBuffer,
textureFormat: .bgra8Unorm
)
else { return }
//
guard let commandQueue = commandQueue,
let commandBuffer = commandQueue.makeCommandBuffer(),
let commandEncoder = commandBuffer.makeComputeCommandEncoder() else {
print("Failed to create a Metal command queue.")
CVMetalTextureCacheFlush(textureCache!, 0)
return
}
commandEncoder.label = "BlendGPU"
commandEncoder.setComputePipelineState(computePipelineState!)
commandEncoder.setTexture(inputTexture, index: 0)
commandEncoder.setTexture(outputTexture, index: 2)
//线
let width = computePipelineState!.threadExecutionWidth
let height = computePipelineState!.maxTotalThreadsPerThreadgroup / width
let threadsPerThreadgroup = MTLSizeMake(width, height, 1)
let threadgroupsPerGrid = MTLSize(width: inputTexture.width,
height: inputTexture.height,
depth: 1)
commandEncoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup)
commandEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
guard let outputPixelBuffer else { return }
self.image = CIImage(cvPixelBuffer: outputPixelBuffer)
}
///' image '
/// -rect:
override func draw(_ rect: CGRect) {
guard let image = image,
let currentDrawable = currentDrawable,
let commandBuffer = commandQueue?.makeCommandBuffer()
else {
return
}
let currentTexture = currentDrawable.texture
let drawingBounds = CGRect(origin: .zero, size: drawableSize)
let scaleX = drawableSize.width / image.extent.width
let scaleY = drawableSize.height / image.extent.height
let scaledImage = image.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY))
context.render(scaledImage, to: currentTexture, commandBuffer: commandBuffer, bounds: drawingBounds, colorSpace: colorSpace)
commandBuffer.present(currentDrawable)
commandBuffer.commit()
}
///' CVPixelBuffer '' MTLTexture '
/// -:
/// - pixelBuffer:
/// - textureFormat:
/// -:' MTLTexture '
private func makeTextureFromCVPixelBuffer(
pixelBuffer: CVPixelBuffer,
textureFormat: MTLPixelFormat
) -> MTLTexture? {
let width = CVPixelBufferGetWidth(pixelBuffer)
let height = CVPixelBufferGetHeight(pixelBuffer)
guard let textureCache else { return nil }
//
var cvTextureOut: CVMetalTexture?
CVMetalTextureCacheCreateTextureFromImage(
kCFAllocatorDefault,
textureCache,
pixelBuffer,
nil,
textureFormat,
width,
height,
0,
&cvTextureOut
)
guard let cvTextureOut,
let texture = CVMetalTextureGetTexture(cvTextureOut)
else {
CVMetalTextureCacheFlush(textureCache, 0)
return nil
}
return texture
}
///' CVPixelBufferPool '
/// -:
/// -:' CVPixelBufferPool '
private func createBufferPool(size: CGSize) -> CVPixelBufferPool? {
let allocationThreshold = 5
let sourcePixelBufferAttributesDictionary = [
kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_32BGRA),
kCVPixelBufferWidthKey as String: NSNumber(value: Float(size.width)),
kCVPixelBufferHeightKey as String: NSNumber(value: Float(size.height)),
kCVPixelBufferMetalCompatibilityKey as String: kCFBooleanTrue!,
kCVPixelBufferIOSurfacePropertiesKey as String: [
kCVPixelBufferIOSurfaceCoreAnimationCompatibilityKey:kCFBooleanTrue,
]
] as [String : Any]
let poolAttributes = [kCVPixelBufferPoolMinimumBufferCountKey as String: allocationThreshold]
var cvPixelBufferPool: CVPixelBufferPool?
CVPixelBufferPoolCreate(kCFAllocatorDefault, poolAttributes as NSDictionary?, sourcePixelBufferAttributesDictionary as NSDictionary?, &cvPixelBufferPool)
return cvPixelBufferPool
}
}