Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

279
Views
Synchronize work between CPU and GPU within single command buffer using MTLSharedEvent

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:

  1. GPU computation part 1
  2. CPU computation based on results from GPU computation part 1
  3. 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

over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

This is perfectly fine that command buffer is committed. In fact if it wouldn't be committed you'll never get to notify block.

GPU and CPU runs in parallel. So when you use MTLEvent you don't stop executing CPU code (all the Swift code actually). You just tell GPU in what order to execute GPU code.

So what's happening in your case:

  1. All your code runs in a single CPU thread without any interruption.
  2. GPU starts executing command buffer commands only when you call commit(). Before it GPU actually don't do anything. You just scheduled command to be performed on GPU but don't perform them.
  3. When GPU executes commands it checks for your MTLEvent. It performs part 1, then encodes value 1 to event, performs notify block, encodes value 2, performs second GPU block.

But again all the actual GPU work starts only when you call commit() on command buffer. That's why buffer is already committed in notify block. Because it is performed after commit().

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!