确切地说,我如何在后台线程上渲染 Metal? [英] How, exactly, do I render Metal on a background thread?

查看:112
本文介绍了确切地说,我如何在后台线程上渲染 Metal?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

This problem is caused by user interface interactions such as showing the titlebar while in fullsreen. That question's answer provides a solution, but not how to implement that solution.

解决方案是在后台线程上渲染.问题是,苹果提供的代码是为了覆盖很多内容,所以大部分都是无关代码,所以即使我能理解它,使用苹果的代码也是不可行的.我无法理解它,所以它只是简单的不是一种选择.如何使用尽可能简洁的后台线程制作简单的 Swift Metal 游戏?

The solution is to render on a background thread. The issue is, the code provided in Apple's is made to cover a lot of content so most of it will extraneous code, so even if I could understand it, it isn't feasible to use Apple's code. And I can't understand it so it just plain isn't an option. How would I make a simple Swift Metal game use a background thread being as concise as possible?

以这个为例:

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

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

That is the start of a basic Metal game. What would that code look like, if it were rendering on a background thread?

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

To fix that bug when showing the titlebar in Metal, I need to render it on a background thread. Well, how do I render it on a background thread?

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

I've noticed this answer suggests to manually redraw it 60 times a second. Presumably using a loop that is on a background thread? But that seems... not a clean way to fix it. Is there a cleaner way?

推荐答案

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

The main trick in getting this to work seems to be setting up the CVDisplayLink. This is awkward in Swift, but doable. After some work I was able to modify the "Game" template in Xcode to use a custom view backed by CAMetalLayer instead of MTKView, and a CVDisplayLink to render in the background, as suggested in the sample code you linked — see below.

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

Edit Oct 22:
The approach mentioned in this thread seems to work just fine: still using an MTKView, but drawing it manually from the display link callback. Specifically I was able to follow these steps:

  1. 在 Xcode 中创建一个新的 macOS 游戏项目.
  2. 修改 GameViewController 以添加 CVDisplayLink,类似于以下内容(有关使用 CVDisplayLink 的更多信息,请参阅这个问题来自斯威夫特).在 viewWillAppear 中启动显示链接,在 viewWillDisappear 中停止.
  3. 在 viewDidLoad 中设置 mtkView.isPaused = true 以禁用自动渲染,而是从显示链接回调中显式调用 mtkView.draw().
  1. Create a new macOS Game project in Xcode.
  2. Modify GameViewController to add a CVDisplayLink, similar to below (see this question for more on using CVDisplayLink from Swift). Start the display link in viewWillAppear and stop it in viewWillDisappear.
  3. Set mtkView.isPaused = true in viewDidLoad to disable automatic rendering, and instead explicitly call mtkView.draw() from the display link callback.

我修改后的 GameViewController.swift 的完整内容在这里可用.

The full content of my modified GameViewController.swift is available here.

我没有检查 Renderer 类的线程安全性,所以我不能确定不需要更多的更改,但这应该能让您正常运行.

I didn't review the Renderer class for thread safety, so I can't be sure no more changes are required, but this should get you up and running.

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

Older implementation with CAMetalLayer instead of MTKView:

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

This is just a proof of concept and I can't guarantee it's the best way to do everything. You might find these articles helpful too:

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)
    }
  }
}

这篇关于确切地说,我如何在后台线程上渲染 Metal?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆