Home > Mobile >  Swift MetalKit buffer completion handler vs waitForCompletion?
Swift MetalKit buffer completion handler vs waitForCompletion?

Time:03-07

When creating a render pass in MetalKit is it better in terms of performance to wait for completion or to add a completion handler? If I use the completion handler then I'll end up with a lot of nested closures, but I think waitForCompletion might block a thread. If the completion handler is preferred, is there a better way in Swift to do this without having to use so many nested closures?

For example,

buffer.addCompletionHandler { _ in
    ... next task
    buffer2.addCompletionHandler { _ in
        ... etc etc
    }
}

CodePudding user response:

The other people are right in telling you that this is probably not what you want to do, and you should go educate yourself on how others have created render loops in Metal.

That said, if you actually have use cases for non-blocking versions of waitUntilCompleted or waitUntilScheduled, you can create and use your own until Apple gets around to providing the same.

public extension MTLCommandBuffer {
  /// Wait until this command buffer is scheduled for execution on the GPU.
  var schedulingCompletion: Void {
    get async {
      await withUnsafeContinuation { continuation in
        addScheduledHandler { _ in
          continuation.resume()
        }
      }
    }
  }

  /// Wait until the GPU has finished executing the commands in this buffer.
  var completion: Void {
    get async {
      await withUnsafeContinuation { continuation in
        addCompletedHandler { _ in
          continuation.resume()
        }
      }
    }
  }
}

But I don't think this is a good idea, as all of the grouping code, necessary to ensure that the "handlers" are added before commit is called, is worse than the callbacks.

let string: String = await withTaskGroup(of: String.self) { group in
  let buffer = MTLCreateSystemDefaultDevice()!.makeCommandQueue()!.makeCommandBuffer()!

  group.addTask {
    await buffer.schedulingCompletion
    return "2"
  }

  group.addTask {
    await buffer.completion
    return "3"
  }

  group.addTask {
    buffer.commit()
    return "1"
  }

  return await .init(group)
}

XCTAssertEqual(string, "123")
public extension String {
  init<AsyncStrings: _Concurrency.AsyncSequence>(_ strings: AsyncStrings) async rethrows
  where AsyncStrings.Element == String {
    self = try await strings.reduce(into: .init()) { $0.append($1) }
  }
}

CodePudding user response:

You are supposed to use MTLCommandQueues and MTLEvents for serializing your GPU work, not completion handlers. Completion handlers are meant to be used only in cases where you need CPU-GPU synchronization. e.g. when you need to read back a result of GPU calculation on a CPU, or you need to add a back pressure, like for example when you only want to draw a certain amount of frame concurrently.

  • Related