Runt
2025-07-09 3b7521a47ae731f0bf0a922822e4417493489539
LiveProject/tool/MetalRenderer.swift
@@ -4,75 +4,99 @@
//  渲染工具
//  Created by 倪路朋 on 6/26/25.
//
import Foundation
import Metal
import MetalKit
import AVFoundation
class Renderer: NSObject, MTKViewDelegate {
    static var shared: Renderer?
class MetalRenderer: NSObject, MTKViewDelegate {
    private let device: MTLDevice
    private let commandQueue: MTLCommandQueue
    private let ciContext: CIContext
    private var device: MTLDevice!
    private var commandQueue: MTLCommandQueue!
    private var pipelineState: MTLRenderPipelineState!
    private var texture: MTLTexture?
    private var textureDescriptor: MTLTextureDescriptor!
    private var currentData: [UInt8]?
    private var textureWidth = 0
    private var textureHeight = 0
    private var currentPixelBuffer: CVPixelBuffer?
    private let textureCache: CVMetalTextureCache
    weak var mtkView: MTKView? {
        didSet {
            guard let view = mtkView else { return }
            device = view.device
            commandQueue = device.makeCommandQueue()
            let library = device.makeDefaultLibrary()
            let pipelineDesc = MTLRenderPipelineDescriptor()
            pipelineDesc.vertexFunction = library?.makeFunction(name: "vertexShader")
            pipelineDesc.fragmentFunction = library?.makeFunction(name: "fragmentShader")
            pipelineDesc.colorAttachments[0].pixelFormat = view.colorPixelFormat
            pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDesc)
            Renderer.shared = self
    init(mtkView: MTKView) {
        guard let device = MTLCreateSystemDefaultDevice(),
              let commandQueue = device.makeCommandQueue() else {
            fatalError("Unable to create Metal device or command queue")
        }
    }
    func updateFrame(data: [UInt8], width: Int, height: Int) {
        currentData = data
        textureWidth = width
        textureHeight = height
        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")
    }
    func draw(in view: MTKView) {
        let size = view.drawableSize
        //print("🧾 drawableSize = \(size)")
        if !size.width.isFinite || !size.height.isFinite || size.width <= 0 || size.height <= 0 {
            print("❌ 非法尺寸,跳过渲染 \(size)")
            return
        }
        guard let drawable = view.currentDrawable,
              let descriptor = view.currentRenderPassDescriptor,
              let data = currentData else { return }
        if texture == nil || texture?.width != textureWidth || texture?.height != textureHeight {
            textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(
                pixelFormat: .rgba8Unorm,
                width: textureWidth,
                height: textureHeight,
                mipmapped: false
            )
            textureDescriptor.usage = [.shaderRead, .shaderWrite]
            texture = device.makeTexture(descriptor: textureDescriptor)
              let commandBuffer = commandQueue.makeCommandBuffer(),
              let pixelBuffer = currentPixelBuffer else {
            return
        }
        let region = MTLRegionMake2D(0, 0, textureWidth, textureHeight)
        data.withUnsafeBytes { ptr in
            texture?.replace(region: region, mipmapLevel: 0, withBytes: ptr.baseAddress!, bytesPerRow: textureWidth * 4)
        }
        let drawableSize = view.drawableSize
        guard drawableSize.width > 0, drawableSize.height > 0 else { return }
        let commandBuffer = commandQueue.makeCommandBuffer()
        let encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor)
        encoder?.setRenderPipelineState(pipelineState)
        encoder?.setFragmentTexture(texture, index: 0)
        encoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
        encoder?.endEncoding()
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
        // 加方向修正:顺时针旋转90度
        var ciImage = CIImage(cvPixelBuffer: pixelBuffer).oriented(.right)
        // 等比缩放后居中
        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) {}
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        // No-op: Handle size change if needed
    }
    func display(pixelBuffer: CVPixelBuffer) {
        self.currentPixelBuffer = pixelBuffer
        //print("display")
        //刷新
    }
}