使用 MTLSharedEvent 在单个命令缓冲区内同步 CPU 和 GPU 之间的工作
Synchronize work between CPU and GPU within single command buffer using MTLSharedEvent
我正在尝试使用 MTLSharedEvent
和 MTLSharedEventListener
来同步 GPU 和 CPU 之间的计算,如 Apple 提供的示例 (https://developer.apple.com/documentation/metal/synchronization/synchronizing_events_between_a_gpu_and_the_cpu)。基本上我想要实现的是将工作分成 3 个部分按顺序执行,如下所示:
- GPU 计算部分 1
- CPU 计算基于 GPU 计算第 1 部分的结果
- CPU 计算后的 GPU 计算第 2 部分
我的问题是 eventListener
块总是在命令缓冲区被安排执行之前调用,这使得我的 CPU 任务按顺序首先执行。
为了简化案例,让我们使用简单的命令填充 MTLBuffer
某些值(我的原始用例更复杂,因为使用带有自定义着色器的计算编码器,但行为相同):
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()
输出:
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
如您所见,eventListener
将缓冲区状态记录为 .commited
。
这是怎么回事?我错过了什么吗?
系统:macOS 12.0.1,Apple M1 Pro,Xcode13.2.1
提交命令缓冲区完全没问题。事实上,如果它不被提交,你将永远不会到达 notify
块。
GPU 和 CPU 并行运行。因此,当您使用 MTLEvent
时,您不会停止执行 CPU 代码(实际上是所有 Swift 代码)。您只需告诉 GPU 以什么顺序执行 GPU 代码。
那么你的情况是怎样的:
- 您的所有代码都在单个 CPU 线程中运行,没有任何中断。
- GPU 仅在您调用
commit()
时才开始执行命令缓冲区命令。在此之前,GPU 实际上什么都不做。您刚刚安排了要在 GPU 上执行的命令,但没有执行它们。
- 当 GPU 执行命令时,它会检查您的
MTLEvent
。它执行第 1 部分,然后将值 1
编码为事件,执行通知块,对值 2
进行编码,执行第二个 GPU 块。
但是,只有当您在命令缓冲区上调用 commit()
时,所有实际的 GPU 工作才开始。这就是缓冲区已经在通知块中提交的原因。因为是在commit()
.
之后执行的
我正在尝试使用 MTLSharedEvent
和 MTLSharedEventListener
来同步 GPU 和 CPU 之间的计算,如 Apple 提供的示例 (https://developer.apple.com/documentation/metal/synchronization/synchronizing_events_between_a_gpu_and_the_cpu)。基本上我想要实现的是将工作分成 3 个部分按顺序执行,如下所示:
- GPU 计算部分 1
- CPU 计算基于 GPU 计算第 1 部分的结果
- CPU 计算后的 GPU 计算第 2 部分
我的问题是 eventListener
块总是在命令缓冲区被安排执行之前调用,这使得我的 CPU 任务按顺序首先执行。
为了简化案例,让我们使用简单的命令填充 MTLBuffer
某些值(我的原始用例更复杂,因为使用带有自定义着色器的计算编码器,但行为相同):
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()
输出:
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
如您所见,eventListener
将缓冲区状态记录为 .commited
。
这是怎么回事?我错过了什么吗?
系统:macOS 12.0.1,Apple M1 Pro,Xcode13.2.1
提交命令缓冲区完全没问题。事实上,如果它不被提交,你将永远不会到达 notify
块。
GPU 和 CPU 并行运行。因此,当您使用 MTLEvent
时,您不会停止执行 CPU 代码(实际上是所有 Swift 代码)。您只需告诉 GPU 以什么顺序执行 GPU 代码。
那么你的情况是怎样的:
- 您的所有代码都在单个 CPU 线程中运行,没有任何中断。
- GPU 仅在您调用
commit()
时才开始执行命令缓冲区命令。在此之前,GPU 实际上什么都不做。您刚刚安排了要在 GPU 上执行的命令,但没有执行它们。 - 当 GPU 执行命令时,它会检查您的
MTLEvent
。它执行第 1 部分,然后将值1
编码为事件,执行通知块,对值2
进行编码,执行第二个 GPU 块。
但是,只有当您在命令缓冲区上调用 commit()
时,所有实际的 GPU 工作才开始。这就是缓冲区已经在通知块中提交的原因。因为是在commit()
.