I have a number of cached MTLTexture's which I would like to set to purgeable using MTLTexture's setPurgeableState API. In order to do this I follow the process outlined in Apple's 2019 WWDC video which suggests:
- Setting cached
MTLTextureinstances tovolatile - Flagging
MTLTextureinstances asnonVolatilewhile 'in use' as part of a command buffer - Using
MTLCommandBuffer'saddCompletedHandlerto set allMTLTextureinstances back tovolatileafter the command buffer completes its work
This approach works great, but quickly runs into issues in a triple buffered renderer where more than one command buffer is in-flight simultaneously. In these instances I receive the following error:
Cannot set purgeability state to volatile while resource is in use by a command buffer.
... which makes sense. I'm obviously attempting to flag a MTLTexture as volatile while it's in-flight as part of a subsequent command buffer. But what's the best way around this without obliterating the performance advantages afforded by triple buffering in the first place?
Attached below is a basic sketch of the approach I'm using (and the one outlined by Apple in the aforementioned WWDC video):
let cache = [String:MTLTexture]()
// Insert a number of textures into CPU cache and set
// their purgeable state to volatile
for (key, texture) in cache {
texture.setPurgeableState(.volatile)
}
// Ensure no more than 3 command buffers are in-flight
// at any given them
semaphore.wait()
// Determine which cached textures are to be used for the
// next render pass and mark them nonVolatile
if cache["texture_to_be_used"].setPurgeableState(.nonVolatile) == .empty {
// If the texture has been purged recreate as necessary...
}
guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }
// Construct command buffer, binding textures where necessary
// ...
// Set a completion handler for the command buffer
commandBuffer.addCompletedHandler({ (buffer) in
// Set any textures marked as nonVoltile back to volatile
// so that they can be purged when required
//
// The issue here is that as I'm using a triple buffered
// approach I have no way of (efficiently) knowing if a given
// texture is being bound as part of another in-flight
// command buffer
for (key, texture) in cache {
texture.setPurgeableState(.volatile)
}
// Rely on a semaphore to manage triple buffered rendering
self.semaphore.signal()
})
commandBuffer.commit()