title: Making use of Metal's volatile MTLPurgeableState while triple buffering
body: <p>I have a number of cached <code>MTLTexture</code>'s which I would like to set to <code>purgeable</code> using <code>MTLTexture</code>'s <a href="https://developer.apple.com/documentation/metal/mtlresource/1515898-setpurgeablestate" rel="nofollow noreferrer">setPurgeableState</a> API. In order to do this I follow the process outlined in Apple's <a href="https://developer.apple.com/videos/play/wwdc2019/606/" rel="nofollow noreferrer">2019 WWDC video</a> which suggests:</p>
  <ul>
  <li>Setting cached <code>MTLTexture</code> instances to <code>volatile</code></li>
  <li>Flagging <code>MTLTexture</code> instances as <code>nonVolatile</code> while 'in use' as part of a command buffer</li>
  <li>Using <code>MTLCommandBuffer</code>'s <code>addCompletedHandler</code> to set all <code>MTLTexture</code> instances back to <code>volatile</code> after the command buffer completes its work</li>
  </ul>
  <p>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:</p>
  <blockquote>
  <p>Cannot set purgeability state to volatile while resource is in use by a command buffer.</p>
  </blockquote>
  <p>... which makes sense. I'm obviously attempting to flag a <code>MTLTexture</code> as <code>volatile</code> 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?</p>
  <p>Attached below is a basic sketch of the approach I'm using (and the one outlined by Apple in the aforementioned WWDC video):</p>
  <pre><code>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[&quot;texture_to_be_used&quot;].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()
  </code></pre>