【问题标题】:CAMetalLayer nextDrawable returning nil because allocation failedCAMetalLayer nextDrawable 返回 nil 因为分配失败
【发布时间】:2020-06-29 10:40:24
【问题描述】:

我是 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 的帖子可以帮助我,但这并没有解决我的问题。

【问题讨论】:

  • 我绝对不会尝试将一个命令行工具与生命周期应用程序融合在一起。
  • 我同意马特的观点。 MTKView 很可能需要成为窗口/视图层次结构的一部分才能正确分配缓冲区。
  • 所以如果我理解正确的话,不可能在框架中定义允许您打开Metal窗口并控制其内容的条目?
  • 您向我们展示的不是一个框架。它是一个命令行工具。它有一个 Main.swift。你说的是app.run。这些都不会在框架中发生。
  • 如果您收到的答复解决了您的问题,请采纳。

标签: swift macos storyboard runtime-error metal


【解决方案1】:

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

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

在创建您的 MTKView 时提供一个大小,然后事情就会“起作用”:

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

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

【讨论】:

    【解决方案2】:

    接受的答案绝对指向这里的真正问题,所以请向他致敬。但就我而言,还有另一个障碍,那就是 Auto-Layout 调用周期。

    程序化自动布局

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

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

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

    我希望,这将有助于像我这样的案例。

    【讨论】:

      猜你喜欢
      • 2020-04-13
      • 1970-01-01
      • 2015-09-20
      • 2016-05-02
      • 1970-01-01
      • 2019-05-22
      • 1970-01-01
      • 1970-01-01
      • 2023-03-10
      相关资源
      最近更新 更多