| | |
| | | // 渲染工具 |
| | | // Created by 倪路朋 on 6/26/25. |
| | | // |
| | | import CoreVideo |
| | | import Foundation |
| | | import Metal |
| | | import MetalKit |
| | | import AVFoundation |
| | | |
| | | class MetalRenderer: NSObject, MTKViewDelegate { |
| | | var device: MTLDevice! |
| | | var commandQueue: MTLCommandQueue! |
| | | private let device: MTLDevice |
| | | private let commandQueue: MTLCommandQueue |
| | | private let ciContext: CIContext |
| | | |
| | | func setup(view: MTKView) { |
| | | self.device = view.device |
| | | self.commandQueue = device.makeCommandQueue() |
| | | // 初始化 texture、pipeline 等 |
| | | private var currentPixelBuffer: CVPixelBuffer? |
| | | private let textureCache: CVMetalTextureCache |
| | | private var rotate:Int = 0; |
| | | |
| | | init(mtkView: MTKView) { |
| | | guard let device = MTLCreateSystemDefaultDevice(), |
| | | let commandQueue = device.makeCommandQueue() else { |
| | | fatalError("Unable to create Metal device or command queue") |
| | | } |
| | | |
| | | self.device = device |
| | | self.commandQueue = commandQueue |
| | | self.ciContext = CIContext(mtlDevice: device) |
| | | |
| | | var tmpCache: CVMetalTextureCache? |
| | | CVMetalTextureCacheCreate(nil, nil, device, nil, &tmpCache) |
| | | guard let textureCache = tmpCache else { |
| | | fatalError("Unable to create texture cache") |
| | | } |
| | | self.textureCache = textureCache |
| | | |
| | | super.init() |
| | | |
| | | // ✅ 设置驱动渲染的关键代码 |
| | | mtkView.device = device |
| | | mtkView.framebufferOnly = false |
| | | mtkView.isPaused = false |
| | | mtkView.enableSetNeedsDisplay = false |
| | | mtkView.delegate = self |
| | | print("MetalRenderer init") |
| | | } |
| | | |
| | | // ✅ 必须实现的方法 1:窗口大小改变时调用 |
| | | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { |
| | | // 可以留空或更新视图缩放、渲染区域等 |
| | | } |
| | | |
| | | // ✅ 必须实现的方法 2:每一帧绘制时调用 |
| | | func draw(in view: MTKView) { |
| | | guard let drawable = view.currentDrawable, |
| | | let descriptor = view.currentRenderPassDescriptor else { return } |
| | | let size = view.drawableSize |
| | | //print("🧾 drawableSize = \(size)") |
| | | |
| | | let commandBuffer = commandQueue.makeCommandBuffer()! |
| | | let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)! |
| | | // 渲染逻辑,如绘制纹理 |
| | | encoder.endEncoding() |
| | | if !size.width.isFinite || !size.height.isFinite || size.width <= 0 || size.height <= 0 { |
| | | print("❌ 非法尺寸,跳过渲染 \(size)") |
| | | return |
| | | } |
| | | guard let drawable = view.currentDrawable, |
| | | let commandBuffer = commandQueue.makeCommandBuffer(), |
| | | let pixelBuffer = currentPixelBuffer else { |
| | | return |
| | | } |
| | | |
| | | let drawableSize = view.drawableSize |
| | | guard drawableSize.width > 0, drawableSize.height > 0 else { return } |
| | | |
| | | // 加方向修正:顺时针旋转90度 |
| | | var orien:CGImagePropertyOrientation = .up; |
| | | switch self.rotate{ |
| | | case 0: |
| | | break; |
| | | case 90: |
| | | orien = .right; |
| | | break; |
| | | case 180: |
| | | orien = .down; |
| | | break; |
| | | case 270: |
| | | orien = .left; |
| | | break; |
| | | default: |
| | | orien = .up; |
| | | } |
| | | print(" roate = \(rotate)") |
| | | var ciImage = CIImage(cvPixelBuffer: pixelBuffer).oriented(orien) |
| | | |
| | | // 等比缩放后居中 |
| | | let sourceExtent = ciImage.extent |
| | | let scaleX = drawableSize.width / sourceExtent.width |
| | | let scaleY = drawableSize.height / sourceExtent.height |
| | | let scale = min(scaleX, scaleY) |
| | | |
| | | let scaledImage = ciImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) |
| | | |
| | | let xOffset = (drawableSize.width - scaledImage.extent.width) / 2 |
| | | let yOffset = (drawableSize.height - scaledImage.extent.height) / 2 |
| | | let translatedImage = scaledImage.transformed(by: CGAffineTransform(translationX: xOffset, y: yOffset)) |
| | | |
| | | // 渲染 |
| | | ciContext.render(translatedImage, |
| | | to: drawable.texture, |
| | | commandBuffer: commandBuffer, |
| | | bounds: CGRect(origin: .zero, size: drawableSize), |
| | | colorSpace: CGColorSpaceCreateDeviceRGB()) |
| | | |
| | | commandBuffer.present(drawable) |
| | | commandBuffer.commit() |
| | | //print("绘制画面") |
| | | } |
| | | |
| | | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { |
| | | // No-op: Handle size change if needed |
| | | } |
| | | |
| | | func display(pixelBuffer: CVPixelBuffer) { |
| | | self.currentPixelBuffer = pixelBuffer |
| | | //print("display") |
| | | //刷新 |
| | | |
| | | } |
| | | |
| | | func updateRotate(angle:Int){ |
| | | rotate = angle; |
| | | } |
| | | |
| | | } |