I have a metal kit view and drain the draw method of its delegate like shown below. Let's say I have one or more MTLBuffers with vertex resources bound via the argument table.
When is it ok to drop these buffers?
As far as I know one cannot schedule a completion handler In Metal 4 and I haven't been able to find any documentation about the lifetime requirements here.
Any pointers/ideas appreciated.
class RenderCoordinator: NSObject, MTKViewDelegate {
public func draw(in view: MTKView) {
let commandAllocator: any MTL4CommandAllocator = ...
let commandBuffer: any MTL4CommandBuffer = ...
let commandQueue: any MTL4CommandQueue = ...
guard let drawable = view.currentDrawable else { return }
commandBuffer.beginCommandBuffer(allocator: commandAllocator)
let state: any MTLRenderPipelineState = ...
let encoder: any MTL4RenderCommandEncoder = ...
let argTable: any MTL4ArgumentTable = ...
encoder.setRenderPipelineState(state)
encoder.setArgumentTable(argTable, stages: .vertex)
commandBuffer.endCommandBuffer()
commandQueue.waitForDrawable(drawable)
commandQueue.commit([commandBuffer])
commandQueue.signalDrawable(drawable)
drawable.present()
}
}
There is no addCompletedHandler equivalent on MTL4CommandBuffer, but the completion signal still exists. It moved to the queue: signalEvent(_:value:) on MTL4CommandQueue, paired with an MTLSharedEvent your renderer waits on.
Understanding the Metal 4 core API (https://developer.apple.com/documentation/metal/understanding-the-metal-4-core-api) states the change:
Unlike the default behavior of
MTLCommandBuffer, you may need to consider a resource's retain count because eachMTL4CommandBufferinstance doesn't create strong references to resources.
The argument table doesn't hold a reference either, because it binds addresses rather than objects:
- (void)setAddress:(MTLGPUAddress)gpuAddress atIndex:(NSUInteger)bindingIndex;
So nothing in the submission path keeps your vertex buffers alive. Something in your own code needs to hold them until the GPU finishes the work that reads them.
Drawing a triangle with Metal 4 (https://developer.apple.com/documentation/metal/drawing-a-triangle-with-metal-4) shows the pattern, running three frames in flight:
- A separate
MTLBufferper in-flight frame, held in an array for the renderer's lifetime - Bindings by
vertexBuffer.gpuAddress - A signal at the end of each frame:
commandQueue.signalEvent(sharedEvent, value: frameNumber) - A wait before reusing a frame's resources:
sharedEvent.wait(untilSignaledValue: frameNumber - kMaxFramesInFlight, timeoutMS: 10)
That wait is what tells you a buffer can be dropped or overwritten. The event fires when the frame that referenced it has finished.
Your submit ordering, waitForDrawable then commit then signalDrawable then present, matches the sample. What isn't there yet is the signalEvent call, and a reference to the buffers that outlives draw(in:).
A residency set covers residency rather than lifetime. The sample keeps its own strong references to the buffers in addition to adding them to one.
MTL4CommitOptions does have an addFeedbackHandler: method, and it does fire after completion. It carries error, GPUStartTime, and GPUEndTime, and is described as debug information, so it suits timing and error reporting more than resource management.
The sample relates to WWDC25 session 205, Discover Metal 4 (https://developer.apple.com/wwdc25/205).