I am trying to use MTLSharedEvent along with MTLSharedEventListener to synchronize computation between GPU and CPU, as in example provided by Apple (https://developer.apple.com/documentation/metal/synchronization/synchronizing_events_between_a_gpu_and_the_cpu). Basically what I want to achieve is have work split into 3 parts executed in order, like so:
- GPU computation part 1
- CPU computation based on results from GPU computation part 1
- GPU computation part 2 after CPU computation
My problem is that eventListener block is always called before command buffer is being scheduled for execution, which make my CPU task execute first in order.
To simplify the case, let’s use simple commands that fill MTLBuffer with certain values (my original use case is more complicated, as using compute encoders with custom shaders, but behaves the same):
let device = MTLCreateSystemDefaultDevice()!
let queue = device.makeCommandQueue()!
let event = device.makeSharedEvent()!
let dispatchQueue = DispatchQueue(label: "myqueue")
let eventListener = MTLSharedEventListener(dispatchQueue: dispatchQueue)
let metalBuffer = device.makeBuffer(length: 2048, options: MTLResourceOptions.storageModeShared)!
let buffer = queue.makeCommandBuffer()!
NSLog("Start - signaled value: \(event.signaledValue)")
event.notify(eventListener, atValue: 1) { event, value in
// CPU work
let pointer = metalBuffer.contents().assumingMemoryBound(to: UInt8.self)
for i in 0..<512 {
(pointer + i).pointee = (pointer + i).pointee + 1;
}
NSLog("Event notification - signaled value: \(value), buffer status: \(buffer.status.rawValue)")
event.signaledValue = 2
}
// GPU work part 1
let encoder1 = buffer.makeBlitCommandEncoder()!
encoder1.fill(buffer: metalBuffer, range: .init(0...127), value: 22)
encoder1.endEncoding()
// signal with 1 to start CPU task
buffer.encodeSignalEvent(event, value: 1)
// wait for value >= 2 to proceed
buffer.encodeWaitForEvent(event, value: 2)
// GPU work part 2
let encoder2 = buffer.makeBlitCommandEncoder()!
encoder2.fill(buffer: metalBuffer, range: .init(128...511), value: 255)
encoder2.endEncoding()
buffer.addScheduledHandler { buffer in
NSLog("Buffer scheduled - signaled value: \(event.signaledValue)")
}
buffer.addCompletedHandler { buffer in
NSLog("Buffer completed - signaled value: \(event.signaledValue)")
}
buffer.commit()
buffer.waitUntilCompleted()
Output:
2022-01-09 23:46:08.774 Sync[76882:3531755] Metal GPU Frame Capture Enabled
2022-01-09 23:46:08.805 Sync[76882:3531755] Start - signaled value: 0
2022-01-09 23:46:08.808 Sync[76882:3531764] Event notification - signaled value: 1, buffer status: 2 (Commited)
2022-01-09 23:46:08.809 Sync[76882:3531763] Buffer scheduled - signaled value: 2
2022-01-09 23:46:08.809 Sync[76882:3531763] Buffer completed - signaled value: 2
As you can see eventListener logs buffer status as .commited.
What’s the matter here? Am I missing something?
System: macOS 12.0.1, Apple M1 Pro, Xcode 13.2.1