SwiftUI 更新降低了 Metal 窗口的 FPS

10
我正在尝试使用SwiftUI和Metal进行实验。我有两个窗口,一个有各种列表和控件,另一个是Metal窗口。

Windows

我曾经让滑块数据更新Metal窗口,但当我移动滑块时,FPS从60下降到25左右。我删除了所有视图之间的链接,但是移动滑块仍会导致Metal窗口的FPS下降。
似乎列表视图也会降低FPS。
我使用以下代码在启动时创建Metal窗口:
    metalWindow = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
                           styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
                           backing: .buffered, defer: false)
    metalWindow.center()
    metalWindow.setFrameAutosaveName("Metal Window")
    metalWindow.makeKeyAndOrderFront(nil)

    mtkView = MTKView()
    mtkView.translatesAutoresizingMaskIntoConstraints = false
    metalWindow.contentView!.addSubview(mtkView)
    metalWindow.contentView!.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[mtkView]|", options: [], metrics: nil, views: ["mtkView" : mtkView!]))
    metalWindow.contentView!.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[mtkView]|", options: [], metrics: nil, views: ["mtkView" : mtkView!]))

    device = MTLCreateSystemDefaultDevice()!
    mtkView.device = device

    mtkView.colorPixelFormat = .bgra8Unorm

    commandQueue = device.makeCommandQueue()!

    mtkView.delegate    = self
    mtkView.sampleCount = 4
    mtkView.clearColor  = MTLClearColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
    mtkView.depthStencilPixelFormat = .depth32Float

控制窗口是一个SwiftUI视图:
struct ControlPanelView: View {

    @ObservedObject var controlPanel = ControlPanel()

    @State private var cameraPos   = Floatx3()
    @State private var lightingPos = Floatx3()

    var body: some View {

        HStack{
            VStack{
                VStack{
                    Text("Objects")
                    List(self.controlPanel.objectFiles) { object in
                        Text(object.name)
                    }
                }
                VStack{
                    Text("Textures")
                    List(self.controlPanel.textureFiles) { texture in
                        HStack {
                            Image(nsImage: texture.image).resizable()
                                .frame(width: 32, height: 32)
                            Text(texture.name)
                        }
                    }
                }
            }
            VStack{
                HStack{
                   XYZControl(heading: "Camera Controls",   xyzPos: $cameraPos)
                   XYZControl(heading: "Lighting Controls", xyzPos: $lightingPos)
                }.padding()
                HStack{
                    Text("Frames Per Second:")
                    //Text(String(renderer.finalFpsCount))
                }
            }
        }.border(Color.red).padding()
    }
}

struct XYZControl: View {

    var heading : String
    @Binding var xyzPos : Floatx3

    var body: some View {

        VStack{
            Text(heading).padding(.bottom, 5.0)
            PositionSlider(sliderValue: $xyzPos.x, label: "X", minimum: -15.0, maximum: 15.0)
            PositionSlider(sliderValue: $xyzPos.y, label: "Y", minimum: -15.0, maximum: 15.0)
            PositionSlider(sliderValue: $xyzPos.z, label: "Z", minimum: -15.0, maximum: 15.0)
        }.border(Color.yellow).padding(.leading)
    }
}

struct PositionSlider: View {

    @Binding var sliderValue : Float
    var label   : String
    var minimum : Float
    var maximum : Float

    static let posFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 3
        return formatter
    }()

    var body: some View {
        VStack{
            Text(label)
            HStack{
                Text("\(Self.posFormatter.string(from:NSNumber(value: minimum))!)")
                Slider(value: $sliderValue, in: minimum ... maximum)
                Text("\(Self.posFormatter.string(from:NSNumber(value: maximum))!)")
            }.padding(.horizontal).frame(width: 150.0, height: 15.0, alignment: .leading)
            Text("\(Self.posFormatter.string(from:NSNumber(value: sliderValue))!)")
        }.border(Color.white)
    }
}

有人能帮忙解决帧率下降的问题吗?

我已经移除了金属窗口,并使用NSViewRepresentable将其合并到控制窗口中,现在看起来像这样: All in one window

但这仍然存在相同的问题。

渲染代码(没有做太多!)。即使不渲染茶壶,它仍会减慢速度。

    func draw(in view: MTKView) {

        if let commandBuffer = commandQueue.makeCommandBuffer() {

            commandBuffer.label = "Frame command buffer"

            if let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: view.currentRenderPassDescriptor!) {

                renderEncoder.label = "render encoder"

                renderEncoder.endEncoding()
            }

            commandBuffer.present(view.currentDrawable!)

            commandBuffer.addCompletedHandler { completedCommandBuffer in
                self.computeFPS()
            }
            commandBuffer.commit()
        }
    }

你如何测量FPS? - Hamid Yusifli
我只是使用一个简单的计时器,从commandBuffer.addCompletedHandler调用。当滑块移动时,金属视图中旋转的茶壶也会减速。 - Leigh
1
我认为你的SwiftUI正在阻塞主线程。 - Hamid Yusifli
2
正如0xBFE1A8所说,SwiftUI正在阻塞主线程。我将绘图函数移动到回调设置中,使用CVDisplayLink .global(qos: .userInteractive)。这样可以防止滑块移动时渲染变慢。 - Leigh
它非常优秀! - Hamid Yusifli
显示剩余3条评论
1个回答

3

正如0xBFE1A8所说,SwiftUI会阻塞主线程。我把绘制函数移动到使用CVDisplayLink .global(qos: .userInteractive)的回调设置中, 这样当滑块被移动时就不会减慢渲染速度了。


我在SwiftUI和MTKView上遇到了同样的问题,但我找不到如何设置回调以使其工作。你能否编辑答案并添加一些代码或伪代码?我想要通过CVDisplayLink回调在后台线程上以固定fps渲染,并且也可以根据事件(mtkView.isPaused = true和调用draw)按需渲染。我不知道如何在Swift中设置链接。当我从后台响应事件调用draw(使用.global(qos: .userInteractive)),它仍然在主线程中执行。我可能漏掉了什么或者不理解这些部分是如何配合的。 - Affinity
@Affinity,我已经有一段时间没有看过这个了,给我几天时间,我会看看我能找到什么。 - Leigh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接