4
votes

I'm currently tracking down some visual popping in my Metal app, and believe it is because I'm drawing directly to framebuffer, not a back-buffer

  // this is when I've finished passing commands to the render buffer and issue the draw command.  I believe this sends all the images directly to the framebuffer instead of using a backbuffer
  [renderEncoder endEncoding];
  [mtlCommandBuffer presentDrawable:frameDrawable];
  [mtlCommandBuffer commit];
  [mtlCommandBuffer release];
  //[frameDrawable present];   // This line isn't needed (and I believe is performed by presentDrawable

Several googles later, I haven't found any documentation of back-buffers in metal. I know I could roll my own, but I can't believe metal doesn't support a back buffer.

Here is the code snippet of how I've setup my CAMetalLayer object.

+ (id)layerClass
{
    return [CAMetalLayer class];
}

- (void)initCommon
{
    self.opaque          = YES;
    self.backgroundColor = nil;
    ...
}

-(id <CAMetalDrawable>)getMetalLayer
{
    id <CAMetalDrawable> frameDrawable;
    while (!frameDrawable && !frameDrawable.texture)
    {
        frameDrawable = [self->_metalLayer nextDrawable];
    }
    return frameDrawable;
}

Can I enable a backbuffer on my CAMetalLayer object, or will I need to roll my own?

1

1 Answers

8
votes

I assume by back-buffer, you mean a renderbuffer that is being rendered to, while the corresponding front-buffer is being displayed?

In Metal, the concept is provided by the drawables that you extract from CAMetalLayer. The CAMetalLayer instance maintains a small pool of drawables (generally 3), retrieves one of them from the pool each time you invoke nextDrawable, and returns it back to the pool after you've invoked presentDrawable and once rendering is complete (which may be some time later, since the GPU runs asynchronously from the CPU).

Effectively, on each frame loop, you grab a back-buffer by invoking nextDrawable, and make it eligible to become the front-buffer by invoking presentDrawable: and committing the MTLCommandBuffer.

Since there are only 3 drawables in the pool, the catch is that you have to manage this lifecycle yourself, by adding appropriate CPU resource synchronization at the time you invoke nextDrawable and in the callback you get once rendering is complete (as per the MTLCommandBuffer addCompletedHandler: callback set-up).

Typically you use a dispatch_semaphore_t for this:

_resource_semaphore = dispatch_semaphore_create(3);

then put the following just before you invoke nextDrawable:

dispatch_semaphore_wait(_resource_semaphore, DISPATCH_TIME_FOREVER);

and this in your addCompletedHandler: callback handler:

dispatch_semaphore_signal(_resource_semaphore);

Have a look at some of the simple Metal sample apps from Apple to see this in action. There is not a lot in terms of Apple documentation on this.