CAMetalLayer nextDrawable 返回 nil 因为分配失败

CAMetalLayer nextDrawable returning nil because allocation failed

我是 Swift 和 Mac 开发的新手,我正在尝试制作一个可以打开 Metal window 的框架(仅限 macOS)。我在 macOS 版本 10.15.2 上使用 Xcode 版本 11.3.1。

我从网上找到的示例开始(macOS/Command Line Tool 项目开始),但我看不出如何更正此错误:

[CAMetalLayer nextDrawable] returning nil because allocation failed.

当使用故事板(macOS/App 项目)创建项目时,window 打开时显示预期的内容(填充为红色)但是当我尝试用代码替换故事板时(macOS/Command Line Tool project) 上面提到的错误执行循环.

Main.swift

import AppKit

class AppDelegate: NSObject, NSApplicationDelegate {
    var window: NSWindow!
    var viewController: ViewController!

    func applicationDidFinishLaunching(_ notification: Notification) {
        NSLog("Start app")

        viewController = ViewController()
        window = NSWindow(contentRect: NSMakeRect(0, 0, 1024, 768),
                          styleMask: .borderless,
                          backing: .buffered,
                          defer: false)

        window.contentViewController = viewController
        window.title = "Hey, new Window!"
        window.backgroundColor = NSColor.blue
        window.orderFrontRegardless()
    }

    func applicationWillTerminate(_ notification: Notification) {
        NSLog("Terminate app")
    }

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }
}

let app = NSApplication.shared
let appDelegate = AppDelegate()
app.delegate = appDelegate

app.run()

ViewController.swift

import Cocoa
import Metal
import MetalKit

class ViewController: NSViewController {

    var mtkView: MTKView!
    var renderer: Renderer!

    override func loadView() {
        mtkView = MTKView()
        self.view = mtkView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let defaultDevice = MTLCreateSystemDefaultDevice() else {
            print("Metal is not supported on this device")
            return
        }

        print("My GPU is: \(defaultDevice)")
        mtkView.device = defaultDevice

        guard let tempRenderer = Renderer(mtkView: mtkView) else {
            print("Renderer failed to initialize")
            return
        }
        renderer = tempRenderer

        mtkView.delegate = renderer
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
}

Renderer.swift

import Foundation
import Metal
import MetalKit

class Renderer : NSObject, MTKViewDelegate {

    let device: MTLDevice
    let commandQueue: MTLCommandQueue

    init?(mtkView: MTKView) {
        device = mtkView.device!
        commandQueue = device.makeCommandQueue()!
    }

    func draw(in view: MTKView) {
        guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }

        guard let renderPassDescriptor = view.currentRenderPassDescriptor else { return }

        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1, 0, 0, 1)

        guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return }

        renderEncoder.endEncoding()

        commandBuffer.present(view.currentDrawable!)

        commandBuffer.commit()
    }

    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {

    }
}

在我的研究中,我发现只有一个 post 可以帮助我使用 NSViewControllerRepresentable,但没有解决我的问题。

这里最直接的问题是您的 CAMetalLayer,支持您的 MTKView 的层具有大小 (0, 0)。因此图层无法为您分配可绘制对象。

您的图层大小为零的原因是您的 MTKView 大小为零,而您的视图大小为零的原因是您创建它时未指定框架。根据文档,"Setting contentViewController causes the window to resize based on the current size of the contentViewController;" 因此您的 window 大小也是零。

在创建您的 MTKView 时提供尺寸,然后事情会 "work":

mtkView = MTKView(frame: NSMakeRect(0, 0, 100, 100))

与上面的评论相呼应,我建议不要使用这种方法,因为 NSApplicationrun() 方法并没有做所有必要的事情来使应用程序成为一个好的 macOS 公民。该应用程序不会有 Dock 磁贴,它不会有主菜单,并且您将无法使用 Cmd+Q 等常见的键盘快捷键,而不需要额外的努力。如果您设计的框架是为了让人们更轻松地以最少的努力创建 windows,那么这就太简单了。 NSApplicationMain 代表您用 public API 执行的所有操作也不可能也不支持。鼓励框架的用户遵循平台设计模式,而不是试图以简单的名义从他们手中夺走控制权。如果他们已经 运行 使用适当的 运行 循环构建成熟的应用程序,您可以代表他们创建 windows,而无需触及 NSApplication 层。

接受的答案绝对指向这里的真正问题,所以请向他致敬。但就我而言,还有另一个障碍,即自动布局调用周期。

程序化自动布局

因此,如果您正在处理一个项目,该项目采用编程方法来管理自动布局,那么您必须在此之后创建您的视图。就我而言,我还添加了 Metal 视图作为另一个子视图控制器。所以在我的案例中,解决方案基本上是在正确的生命周期方法中调用。

我刚刚在 viewDidLayoutSubviews() 方法中移动了我的方法调用(添加子视图控制器)。

override func viewDidLayoutSubviews() {
    add(self.cameraVC, frame: container.frame)
}

希望这对像我这样的案例有所帮助。