【问题标题】:How, exactly, do I render Metal on a background thread?究竟如何在后台线程上渲染 Metal?
【发布时间】:2020-12-21 21:11:21
【问题描述】:

This problem 是由用户界面交互引起的,例如在全屏时显示标题栏。该问题的答案提供了一个解决方案,但没有提供如何实施该解决方案。

解决办法是render on a background thread。问题是,苹果提供的代码覆盖了很多内容,所以大部分都是无关代码,所以即使我能理解,使用苹果的代码也不可行。而且我无法理解它,所以它只是简单的不是一种选择。如何让一个简单的 Swift Metal 游戏使用尽可能简洁的后台线程?

以这个为例:

class ViewController: NSViewController {
    var MetalView: MTKView {
        return view as! MTKView
    }
    
    var Device: MTLDevice = MTLCreateSystemDefaultDevice()!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        MetalView.delegate = self
        MetalView.device = Device
        MetalView.colorPixelFormat = .bgra8Unorm_srgb
        Device = MetalView.device
        //setup code
    }
}

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

    }
    
    func draw(in view: MTKView) {
        //drawing code
    }
}

这是基本金属游戏的开始。如果该代码在后台线程上呈现,该代码会是什么样子?

要修复在 Metal 中显示标题栏时的错误,我需要在后台线程上渲染它。那么,如何在后台线程上渲染呢?

我注意到this 的答案建议每秒手动重绘 60 次。大概使用后台线程上的循环?但这似乎......不是一个干净的方法来解决它。有没有更清洁的方法?

【问题讨论】:

  • 示例代码的要点是在需要刷新显示时使用CVDisplayLink在后台触发渲染。您是否下载并尝试了示例代码,您遇到了什么问题?
  • @jtbandes 我下载了 Apple 的示例代码,它在很大程度上解决了我的问题,除了我不知道如何在 Apple 的代码中实现修复。还有没有核心视频的方法?
  • @jtbandes 请编辑以包含您修改后的游戏模板
  • 我不再拥有它,但我认为主要的变化是从视图的 CAMetalLayer 而不是 MTKView 获取 drawable。我没有发布它,因为我没有很好地组织它,我认为如果你自己更新它,你会想出更好的结果。我也认为只要您使用带有显示链接的自定义金属视图,您就不需要完整的游戏示例。
  • @jtbandes 我收到了您的评论,感谢您的回复,但我主要担心的是,当我尝试将 MetalView 的类型更改为 CAMetalLayer 时,我无法使用很多功能不知道如何渲染,例如 currentDrawable 属性和 currentRenderPassDescriptor 或类似的东西

标签: swift multithreading metal


【解决方案1】:

让它工作的主要技巧似乎是设置 CVDisplayLink。这在 Swift 中很尴尬,但可行。经过一些工作后,我能够修改 Xcode 中的“游戏”模板,以使用由 CAMetalLayer 支持的自定义视图而不是 MTKView,以及在后台呈现的 CVDisplayLink,如您链接的示例代码中所建议的那样 - 见下文。


10 月 22 日编辑:
this thread 中提到的方法似乎工作得很好:仍然使用 MTKView,但从显示链接回调中手动绘制它。具体来说,我能够按照以下步骤操作:

  1. 在 Xcode 中创建一个新的 macOS 游戏项目。
  2. 修改 GameViewController 以添加 CVDisplayLink,如下所示(有关使用 Swift 中的 CVDisplayLink 的更多信息,请参阅this question)。在 viewWillAppear 中启动显示链接,在 viewWillDisappear 中停止。
  3. 在 viewDidLoad 中设置 mtkView.isPaused = true 以禁用自动渲染,而是从显示链接回调中显式调用 mtkView.draw()

我修改后的 GameViewController.swift 的全部内容在here 可用。

我没有检查 Renderer 类的线程安全性,所以我不能确定是否需要进行更多更改,但这应该可以让您启动并运行。


使用 CAMetalLayer 而不是 MTKView 的旧实现:

这只是概念验证,我不能保证它是做所有事情的最佳方式。您可能会发现这些文章也很有帮助:

class MyMetalView: NSView {
  var displayLink: CVDisplayLink?
  var metalLayer: CAMetalLayer!

  override init(frame frameRect: NSRect) {
    super.init(frame: frameRect)
    setupMetalLayer()
  }
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    setupMetalLayer()
  }
  override func makeBackingLayer() -> CALayer {
    return CAMetalLayer()
  }
  func setupMetalLayer() {
    wantsLayer = true
    metalLayer = layer as! CAMetalLayer?
    metalLayer.device = MTLCreateSystemDefaultDevice()!
    // ...other configuration of the metalLayer...
  }

  // handle display link callback at 60fps
  static let _outputCallback: CVDisplayLinkOutputCallback = { (displayLink, inNow, inOutputTime, flagsIn, flagsOut, context) -> CVReturn in
    // convert opaque context pointer back into a reference to our view
    let view = Unmanaged<MyMetalView>.fromOpaque(context!).takeUnretainedValue()

    /*** render something into view.metalLayer here! ***/

    return kCVReturnSuccess
  }

  override func viewDidMoveToWindow() {
    super.viewDidMoveToWindow()

    guard CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) == kCVReturnSuccess,
      let displayLink = displayLink
      else {
        fatalError("unable to create display link")
    }

    // pass a reference to this view as an opaque pointer
    guard CVDisplayLinkSetOutputCallback(displayLink, MyMetalView._outputCallback, Unmanaged<MyMetalView>.passUnretained(self).toOpaque()) == kCVReturnSuccess else {
      fatalError("unable to configure output callback")
    }

    guard CVDisplayLinkStart(displayLink) == kCVReturnSuccess else {
      fatalError("unable to start display link")
    }
  }

  deinit {
    if let displayLink = displayLink {
      CVDisplayLinkStop(displayLink)
    }
  }
}

【讨论】:

  • 感谢您的回答
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-29
  • 2018-12-30
  • 2011-06-07
相关资源
最近更新 更多