You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用@Published时CPU占用过高,MTKView渲染卡顿如何解决?

问题分析与解决方案

核心问题1:重复启动接收线程导致帧请求堆积

你的ContentView在navigationDestination中再次创建了ContentView(),每次新视图出现都会调用ndi_c.StartRecv(),而NDI是单例,这会导致多个后台while循环同时运行,不断向主线程发送帧更新请求,直接造成主线程过载。

核心问题2:无限制的主线程更新

即使只启动一次接收,60fps的帧会无间隔地通过DispatchQueue.main.async发送更新请求,主线程来不及处理所有请求,导致任务堆积、帧率波动。

核心问题3:SwiftUI视图频繁重绘

@Published的NDI_Frame每次更新都会触发NDIView的重绘,频繁重绘会进一步增加主线程负担。


具体修复步骤

1. 限制接收线程只启动一次

修改NDI类的StartRecv(),添加启动检查:

func StartRecv(){
    guard !Connected else { return } // 已在接收则直接返回,避免重复启动
    Connected = ndi_t!.startRecv(SelectedSource ?? "")

    NDI_Queue.async {
        while(self.Connected){
            if let buf = self.PrepareFrame(){
                let ciImage = CIImage(cvPixelBuffer: buf)
                self.frameSubject.send(ciImage)
            }
        }
    }
}

同时添加停止接收的方法:

func StopRecv(){
    Connected = false
    ndi_t?.stopRecv()
}

在ContentView的NDIView上添加onDisappear停止接收:

NDIView(imagePublisher: self.ndi_c.$NDI_Frame.eraseToAnyPublisher())
    .onAppear(){
        ndi_c.StartRecv()
        ARData.shared.HideARView = NDIView_Top
    }
    .onDisappear(){
        ndi_c.StopRecv()
    }

2. 控制主线程更新频率,避免任务堆积

改用Combine的throttle限制帧更新频率,确保主线程最多以60fps处理更新:
修改NDI类:

class NDI: ObservableObject{
    private let frameSubject = PassthroughSubject<CIImage?, Never>()
    @Published var NDI_Frame: CIImage?
    let NDI_Queue = DispatchQueue(label: "NDI_recv", qos: .userInitiated) // 给后台线程设置合适优先级
    static var shared = NDI()
    var ndi_t: NDIWrapper?
    var Connected: Bool = false
    
    init(){
        ndi_t = NDIWrapper()
        // 每秒最多60次更新,只取最新帧,避免主线程被淹没
        frameSubject
            .throttle(for: .seconds(1/60), scheduler: DispatchQueue.main, latest: true)
            .assign(to: &$NDI_Frame)
    }

    // ... 其他代码保持不变

    func StartRecv(){
        guard !Connected else { return }
        Connected = ndi_t!.startRecv(SelectedSource ?? "")

        NDI_Queue.async {
            while(self.Connected){
                if let buf = self.PrepareFrame(){
                    let ciImage = CIImage(cvPixelBuffer: buf)
                    self.frameSubject.send(ciImage) // 发送到subject,由throttle控制频率
                }
            }
        }
    }
}

3. 避免重复创建ContentView

你的navigationDestination中.MainView返回ContentView(),会创建新的视图实例,造成不必要的资源消耗。改为弹出到根视图替代:

case .MainView:
    navigationPath.removeLast(navigationPath.count) // 直接回到根视图,无需创建新实例

4. 优化MTKView渲染效率

确保NDIView的渲染逻辑完全在GPU上完成:

  • 复用同一个CIContext(用Metal设备创建),不要每次渲染都新建
  • 渲染逻辑放在Metal的渲染循环中,不要依赖SwiftUI的重绘触发

额外优化建议

  • 检查CVPixelBuffer的格式是否与MTKView兼容,避免额外的格式转换开销
  • 后台处理帧时尽量减少内存拷贝,优先复用缓冲区
  • 用Instruments的Time Profiler工具查看主线程耗时任务,定位具体卡顿点

内容的提问来源于stack exchange,提问作者Shimamura Uzuki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 21:50:27