Making use of Metal's volatile MTLPurgeableState while triple buffering

Viewed 193

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 MTLTexture instances to volatile
  • Flagging MTLTexture instances as nonVolatile while 'in use' as part of a command buffer
  • Using MTLCommandBuffer's addCompletedHandler to set all MTLTexture instances back to volatile after 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()
0 Answers
Related