首页 > 解决方案 > CAMetalLayer nextDrawable 返回 nil 因为分配失败

问题描述

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

我从我在网上找到的示例开始(macOS/命令行工具项目开始),我看不到如何纠正这个错误:

[CAMetalLayer nextDrawable] 因为分配失败而返回 nil。

当使用情节提要(macOS/App 项目)创建项目时,窗口会打开并显示预期的内容(填充为红色),但是当我尝试用代码(macOS/命令行工具项目)替换情节提要时,执行循环会出现错误上文提到的 。

主.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.
        }
    }
}

渲染器.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) {

    }
}

在我的研究中,我发现只有一篇可以帮助我的帖子正在使用NSViewControllerRepresentable但并没有解决我的问题。

标签: swiftmacosstoryboardruntime-errormetal

解决方案


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

您的图层大小为零的原因是因为您的MTKView大小为零,而您的视图大小为零的原因是因为您在没有指定框架的情况下创建了它。根据文档,“设置contentViewController会导致窗口根据 ; 的当前大小调整大小contentViewController” 因此您的窗口大小也为零。

在创建你的东西​​时提供一个大小MTKView,事情会“工作”:

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

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


推荐阅读