Draw something to CAMetalLayer cause it shows all red

I am writing something about video decoding. Now I have met some problems on darwin.

First init the layer and bind it to a view

CAMetalLayer *layer = [[CAMetalLayer alloc] init];
layer.device = MTLCreateSystemDefaultDevice();
layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
layer.drawableSize = self.view.frame.size;
layer.contentsGravity = kCAGravityCenter;
self.view.wantsLayer = YES;
self.view.layer = layer;

next when the video decoded frame coming,

id <CAMetalDrawable> drawable = [metal_layer_ nextDrawable];
if (!drawable) {
  return;
}
id <MTLTexture> texture = [drawable texture];
IOSurfaceRef surface = [texture iosurface];
void *base_address = IOSurfaceGetBaseAddress(surface);
int pitch[] = {(int) (IOSurfaceGetBytesPerRow(surface)), 0};
uint8_t *pixels[] = {static_cast<uint8_t *>(base_address), nullptr};
fn(pixels, pitch); // Which is ffmpeg swscale.
[drawable present];

This does not work at all, the whole screen is red and would never change while new frame is coming. And I tried another way to present the video frame. It is still red.

Is there any thing wrong or missed.

Replies

Why are you using a CAMetalLayer if you don't use any feature from Metal? Your example looks like a very uncommon usage to me:

  • in my experience you get red output when the framebuffer isn't even cleared because there is no render pipeline (MTLRenderCommandEncoder) scheduled to render into that drawable
  • CAMetalLayer provides drawable that cannot be written to by default, only rendered into, at least from Metal pipeline perspective.
  • Is your texture storage even shared? I would expect the texture created by CAMetalLayer to have private storage, so you can only write to it through Metal commands.