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 MTLCommandQueue
s and MTLEvent
s 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.