如何从后台线程将 CIImage 渲染到 CAMetalLayer

问题描述

是否可以从后台线程渲染到 cametallayer,如下所示。请注意,我已注释掉 dispatchQueue.global.async{},因为它会生成 SwiftUI 错误,因为更新必须来自主线程。

如果不是,那么避免阻塞 UI 线程的正确/最佳方法是什么 - 如果这可能的话。我想在用户拖动调整滑块时进行渲染,但如果图像尺寸太大,UI 似乎会变得不稳定,从而影响性能

不知何故,Pixelmator Pro 似乎实现了一种方法,可以将调整应用于图像,而不会出现明显的 UI 延迟或卡顿。任何建议将不胜感激。

func display(_ layer: CALayer) {
        
        // dispatchQueue.global(qos: .userInitiated).async {
        
        if let drawable = self.MetalLayer.nextDrawable(),let commandBuffer = self.commandQueue.makeCommandBuffer() {
            
            
            let colorAttachment = self.passDescriptor.colorAttachments[0]!
            colorAttachment.texture = drawable.texture
            colorAttachment.loadAction = .clear
            colorAttachment.storeAction = .dontCare
            colorAttachment.clearColor = MTLClearColor(red: 0,green: 0,blue: 0,alpha: 0)
            
            if let rawFilter = self.rawFilter {
                
                // required in order to clear the screen if no selection
                let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: self.passDescriptor)!
                renderEncoder.endEncoding()
                
                
                if let processed = self.process(rawFilter) {
                    
                    let x = self.size.width/2 - processed.extent.width/2
                    let y = self.size.height/2 - processed.extent.height/2
                    
                    
                    self.context.render(processed,to: drawable.texture,commandBuffer: commandBuffer,bounds: CGRect(x:-x,y:-y,width: self.size.width,height:  self.size.height),colorSpace: self.colorSpace)
                    
                    
                    
                }
            }
            else {
                // required in order to clear the screen if no selection
                let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: self.passDescriptor)!
                renderEncoder.endEncoding()
                
            }
            
            commandBuffer.commit()
            commandBuffer.waitUntilScheduled()
            
            drawable.present()
            
            // }
        }
    }

解决方法

好的,我刚刚弄清楚为什么我的解决方案不起作用 - 我在 process() 函数中创建了一个图像直方图并设置了一个 UI 控件图像 - 这只是需要包装在一个 DispatchQueue.main.async{} 调用中.

为了防止响应滑块移动而调用过多,除非渲染周期已完成,否则不要调用它

var isBusy = false

func display(_ layer: CALayer) {
    
   guard !self.isBusy else {
       return
   }
   self.isBusy = true

    DispatchQueue.global(qos: .userInitiated).async {
    
    if let drawable = self.metalLayer.nextDrawable(),let commandBuffer = self.commandQueue.makeCommandBuffer() {
        
        
        let colorAttachment = self.passDescriptor.colorAttachments[0]!
        colorAttachment.texture = drawable.texture
        colorAttachment.loadAction = .clear
        colorAttachment.storeAction = .dontCare
        colorAttachment.clearColor = MTLClearColor(red: 0,green: 0,blue: 0,alpha: 0)
        
        if let rawFilter = self.rawFilter {
            
            // Required in order to clear the screen if no selection
            let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: self.passDescriptor)!
            renderEncoder.endEncoding()
            
            
            if let processed = self.process(rawFilter) {
                
                let x = self.size.width/2 - processed.extent.width/2
                let y = self.size.height/2 - processed.extent.height/2
                
                
                self.context.render(processed,to: drawable.texture,commandBuffer: commandBuffer,bounds: CGRect(x:-x,y:-y,width: self.size.width,height:  self.size.height),colorSpace: self.colorSpace)
                
                
                
            }
        }
        else {
            // Required in order to clear the screen if no selection
            let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: self.passDescriptor)!
            renderEncoder.endEncoding()
            
        }
        
        commandBuffer.commit()
        commandBuffer.waitUntilScheduled()
        
        // Present on the main thread - not sure if this is necessary but it seems like it to get the UI to update
        DispatchQueue.main.async {
           drawable.present()
        }
        self.isBusy = false
        }
    }
}