Metal alpha channel oddity

I'm trying to render to a

MTLTexture
within an
MTKView
and then pull the entire region back off the GPU and have an alpha pixel values of clear RGBA=0x00000000 really come off the GPU as just that -- in those regions where I haven't drawn anything.


Instead, all un-rendered areas come off as bright red, which is typical for how Metal renders areas on-screen that have otherwise not been rendered to by a vertex/fragment shader.


However, I've narrowed it down to this test case -- in which I don't actually render anything and send a clear texture on a round trip to the GPU and back:

  • I create my own texture ('clearTexture') with 0x00000000 pixel values
  • This texture ends up the GPU (
    MTLStorageModeManaged
    )
  • I then pull that texture off the GPU ('testTexture')
  • I grab that texture's pixel bytes
  • What do I notice? Pixel values of 0x0000ffff (pure red)


I'm baffled. Any help would be appreciated (and Happy New Year!).

Here's the setup:

CGSize textureSize = CGSizeMake(1920,1080);

// create metalTextureDescriptor
MTLTextureDescriptor *metalTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
                                                                            width:textureSize.width height:textureSize.height mipmapped:NO];
metalTextureDescriptor.storageMode = MTLStorageModeManaged;
metalTextureDescriptor.usage = MTLTextureUsageUnknown;

// create clear Texture
vector_uchar4 clearPixel = {0x00,0x00,0x00,0x00};
id<MTLTexture> clearTexture = [self fillTextureOfSize:textureSize withPixel:clearPixel];

// pull texture off GPU
id<MTLCommandBuffer> commandBuffer = [metalCommandQueue commandBuffer];
id<MTLTexture> testTexture = [self pullTextureOffGPU:clearTexture
                                   withCommandBuffer:commandBuffer];

// a bunch of other rendering (I leave 'testTexture' untouched)

[commandBuffer commit];
[commandBuffer waitUntilCompleted];

// I then check out the pixels
[self debugTexture:testTexture];

At this point, when I break in the debug texture routine (below), the pixel bytes seen are 0x0000ffff and not the expected 0x00000000.


-(id<MTLTexture>)fillTextureOfSize:(CGSize)size withPixel:(vector_uchar4)pixel
{
    id<MTLTexture> texture = [self.device newTextureWithDescriptor:metalTextureDescriptor];
    NSUInteger pixelCount = size.width*size.height;
    vector_uchar4 *buff = malloc(pixelCount*sizeof(vector_uchar4));
    for (NSUInteger i=0; i<pixelCount; i++)
        buff[i]=pixel;
    [blackTexture replaceRegion:MTLRegionMake2D(0, 0, size.width, size.height)
                    mipmapLevel:0
                      withBytes:buff
                    bytesPerRow:size.width*sizeof(vector_uchar4)];
    free(buff);
    return texture;
}


-(id<MTLTexture>) pullTextureOffGPU:(id<MTLTexture>)inputTexture
                  withCommandBuffer:(id<MTLCommandBuffer>)commandBuffer
{
    id<MTLTexture> retTexture = [self.device newTextureWithDescriptor:metalTextureDescriptor];
    if (retTexture) {
        id<MTLBlitCommandEncoder> blit = [commandBuffer blitCommandEncoder];
        [blit copyFromTexture:inputTexture
                  sourceSlice:0
                  sourceLevel:0
                 sourceOrigin:MTLOriginMake(0, 0, 0)
                   sourceSize:MTLSizeMake(inputTexture.width,inputTexture.height,1)
                    toTexture:retTexture
             destinationSlice:0
             destinationLevel:0
            destinationOrigin:MTLOriginMake(0, 0, 0)];
        [blit synchronizeTexture:retTexture slice:0 level:0];
        [blit endEncoding];
    }
    return retTexture;
}


-(void)debugTexture:(id<MTLTexture>)theTexture
{
    NSInteger width = theTexture.width;
    NSInteger height = theTexture.height;
    void *buffer = malloc(width*height*4);
    [theTexture getBytes:buffer
             bytesPerRow:width*4
              fromRegion:MTLRegionMake2D(0,0,width,height)
             mipmapLevel:0];
    free(buffer);  // break here to examine buffer
}