Sources/IOSurface2D.mm

/*
 Copyright (C) 2015 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 Utility class for creating a 2D I/O surface with immutable properties.
 */
 
#import <CoreVideo/CVPixelBuffer.h>
 
#import "CGImageCopy.h"
#import "NSBitmap.h"
#import "IOSurface2D.h"
 
enum : long
{
    kCVPixelFormatType_24RGB_BPP      = 3,
    kCVPixelFormatType_32RGBA_BPP     = 4,
    kCVPixelFormatType_48RGB_BPP      = 6,
    kCVPixelFormatType_64RGBAHalf_BPP = 8
};
 
static const size_t kIOSurface2DCount = 6;
 
static const void* kIOSurface2DKeys[kIOSurface2DCount] =
{
    kIOSurfaceWidth,
    kIOSurfaceHeight,
    kIOSurfacePixelFormat,
    kIOSurfaceBytesPerElement,
    kIOSurfaceBytesPerRow,
    kIOSurfaceAllocSize
};
 
#define CGBitmapIsRGBA(bpp,bpc)    (((bpp / bpc) % 2) == 0)
#define CGBitmapCheckBPP(bpp)      ((bpp == 24) || (bpp == 32) || (bpp == 48) || (bpp == 64))
#define CGBitmapHasAlpha(bpp)      ((bpp == 32) || (bpp == 64))
#define CGBitmapIs8BPC(bpp)        ((bpp == 24) || (bpp == 32))
#define CGBitmapGetChannels(bpp)   (((bpp == 24) || (bpp == 48)) ? 3 : 4)
#define CGBitmapGetByteOrder(bpp)  (CGBitmapIs8BPC(bpp) ? kCGBitmapByteOrder32Host : kCGBitmapByteOrder16Host)
 
@implementation IOSurface2D
{
@private
    BOOL  _isRGBA;
    BOOL  _avoidSync;
    
    size_t  _bytesPerPixel;
    size_t  _width;
    size_t  _height;
    size_t  _samplesPerPixel;
    size_t  _bitsPerComponent;
    size_t  _bitsPerPixel;
    size_t  _bytesPerRow;
    size_t  _size;
    
    uint32_t  _byteOrder;
    uint32_t  _bitmapInfo;
    
    OSType            _format;
    IOSurfaceRef      _surface;
    CGImageAlphaInfo  _alphaInfo;
    
    BOOL                  isFinalized;
    uint32_t              seed;
    IOSurfaceLockOptions  options;
}
 
//-----------------------------------------------------------------------------
//
// FIXME: Replace with the correct values once deep images are fully supported!
//
//-----------------------------------------------------------------------------
 
- (void) _initSurfaceRGBA
{
    _bitsPerComponent = _bitsPerPixel / CGBitmapGetChannels(_bitsPerPixel);
    
    _isRGBA = CGBitmapIsRGBA(_bitsPerPixel, _bitsPerComponent);
    
    if(!_isRGBA)
    {
        _bitsPerPixel = 4 * _bitsPerComponent;
    } // if
} // _initSurfaceRGBA
 
- (void) _initSurfaceInfo
{
    if(!_isRGBA)
    {
        _bitsPerPixel = 4 * _bitsPerComponent;
        _alphaInfo    = kCGImageAlphaNoneSkipLast;
    } // if
    else
    {
        _alphaInfo = CGBitmapHasAlpha(_bitsPerPixel)
        ? kCGImageAlphaPremultipliedLast
        : kCGImageAlphaNone;
    } // else
    
    _byteOrder  = CGBitmapGetByteOrder(_bitsPerPixel);
    _bitmapInfo = _alphaInfo | _byteOrder;
} // _initSurfaceInfo
 
- (void) _initSurfaceSize
{
    _samplesPerPixel = _bitsPerPixel / _bitsPerComponent;
    _bytesPerPixel   = _bitsPerPixel / 8;
    _bytesPerRow     = _width  * _bytesPerPixel;
    _size            = _height * _bytesPerRow;
} // _initSurfaceSize
 
- (void) _initSurfaceType
{
    switch(_bytesPerPixel)
    {
        case kCVPixelFormatType_24RGB_BPP:
            _format = kCVPixelFormatType_24RGB;
            break;
            
        case kCVPixelFormatType_48RGB_BPP:
            _format = kCVPixelFormatType_48RGB;
            break;
            
        case kCVPixelFormatType_64RGBAHalf_BPP:
            _format = kCVPixelFormatType_64RGBAHalf;
            break;
            
        default:
        case kCVPixelFormatType_32RGBA_BPP:
            _format = kCVPixelFormatType_32RGBA;
            break;
    } // switch
} // _initSurfaceType
 
- (BOOL) _initSurfaceProperties
{
    BOOL success = CGBitmapCheckBPP(_bitsPerPixel);
    
    if(success)
    {
        [self _initSurfaceRGBA];
        [self _initSurfaceInfo];
        [self _initSurfaceSize];
        [self _initSurfaceType];
    } // if
    
    return success;
} // _initSurfaceProperties
 
- (BOOL) _initSurfaceProperties:(nullable CGImageRef)pImage
{
    size_t nBPP = CGImageGetBitsPerPixel(pImage);
    
    BOOL success = CGBitmapCheckBPP(nBPP);
    
    if(success)
    {
        _bitsPerPixel     = nBPP;
        _bitsPerComponent = CGImageGetBitsPerComponent(pImage);
        
        _isRGBA = CGBitmapIsRGBA(_bitsPerPixel, _bitsPerComponent);
        
        if(!_isRGBA)
        {
            _bitsPerPixel = 4 * _bitsPerComponent;
            _alphaInfo    = kCGImageAlphaNoneSkipLast;
            _byteOrder    = CGBitmapGetByteOrder(_bitsPerPixel);
            _bitmapInfo   = _alphaInfo | _byteOrder;
        } // if
        else
        {
            _alphaInfo  = CGImageGetAlphaInfo(pImage);
            _bitmapInfo = CGImageGetBitmapInfo(pImage);
            _byteOrder  = _bitmapInfo & kCGBitmapByteOrderMask;
        } // else
        
        _width  = CGImageGetWidth(pImage);
        _height = CGImageGetHeight(pImage);
        
        [self _initSurfaceSize];
        [self _initSurfaceType];
    } // if
    
    return success;
} // _initSurfaceProperties
 
- (nullable IOSurfaceRef) _newSurface
{
    IOSurfaceRef surface = nullptr;
    
    const void* values[kIOSurface2DCount];
    
    values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_width);
    values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_height);
    values[2] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType,  &_format);
    values[3] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_bytesPerPixel);
    values[4] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_bytesPerRow);
    values[5] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_size);
    
    CFDictionaryRef  properties = CFDictionaryCreate(kCFAllocatorDefault,
                                                     kIOSurface2DKeys,
                                                     values,
                                                     kIOSurface2DCount,
                                                     &kCFTypeDictionaryKeyCallBacks,
                                                     &kCFTypeDictionaryValueCallBacks);
    
    if(properties != nullptr)
    {
        surface = IOSurfaceCreate(properties);
                
        CFRelease(properties);
    } // if
    
    size_t i;
    
    for(i = 0; i < kIOSurface2DCount; ++i)
    {
        if(values[i] != nullptr)
        {
            CFRelease(values[i]);
        } // if
    } // for
    
    return surface;
} // _newSurface
 
- (BOOL) _newSurface:(nullable CGImageRef)pImage
{
    isFinalized = pImage != nullptr;
    
    if(isFinalized)
    {
        isFinalized = [self _initSurfaceProperties:pImage];
        
        if(isFinalized)
        {
            _surface = [self _newSurface];
            
            isFinalized = _surface != nullptr;
        } // if
    } // if
    
    return isFinalized;
} // _newSurface
 
- (BOOL) _drawImage:(nullable CGImageRef)pImage
{
    BOOL success = [self _newSurface:pImage];
    
    if(success)
    {
        CGColorSpaceRef pColorSpace = CGColorSpaceCreateDeviceRGB();
        
        if(pColorSpace != nullptr)
        {
            IOReturn result = IOSurfaceLock(_surface, 0, &seed);
            
            success = result == kIOReturnSuccess;
            
            if(success)
            {
                void* pBaseAddr = IOSurfaceGetBaseAddressOfPlane(_surface, 0);
                
                if(pBaseAddr != nullptr)
                {
                    CGContextRef pContext = CGBitmapContextCreate(pBaseAddr,
                                                                  _width,
                                                                  _height,
                                                                  _bitsPerComponent,
                                                                  _bytesPerRow,
                                                                  pColorSpace,
                                                                  _bitmapInfo);
                    
                    if(pContext != nullptr)
                    {
                        CGRect bounds = CGRectMake(0, 0, _width, _height);
                        
                        CGContextDrawImage(pContext, bounds, pImage);
                        
                        CFRelease(pContext);
                    } // if
                } // if
                
                IOSurfaceUnlock(_surface, 0, &seed);
            } // if
            
            CFRelease(pColorSpace);
        } // if
    } // if
    
    return success;
} // _drawImage
 
- (BOOL) _initSurfaceWithImageSource:(nullable CGImageSourceRef)pImageSource
{
    BOOL success = pImageSource != nullptr;
    
    if(success)
    {
        CGImageRef pImage = CGImageSourceCreateImageAtIndex(pImageSource,
                                                            0,
                                                            NULL);
        
        success = pImage != nullptr;
        
        if(success)
        {
            success = [self _drawImage:pImage];
            
            CFRelease(pImage);
        } // if
    } // if
    
    return success;
} // _initSurfaceWithImageSource
 
- (BOOL) _initSurfaceWithImage:(nullable NSImage *)image
{
    BOOL success = NO;
    
    if(image)
    {
        NSBitmapImageRep* bitmap = [NSBitmap bitmapWithImage:image].bitmap;
        
        if(bitmap)
        {
            success = !bitmap.isPlanar;
            
            if(success)
            {
                success = [self _drawImage:bitmap.CGImage];
            } // if
        } // if
    } // if
    
    return success;
} // _initSurfaceWithImage
 
- (void) _initSurfaceWithURL:(nullable NSURL *)url
{
    if(url)
    {
        // Create an image source from the URL.
        CGImageSourceRef pImageSource = CGImageSourceCreateWithURL(CFURLRef(url), nullptr);
        
        if(pImageSource != nullptr)
        {
            [self _initSurfaceWithImageSource:pImageSource];
            
            CFRelease(pImageSource);
        } // if
    } // if
} // _initSurfaceWithFile
 
 
- (void) _initSurfaceWithFile:(nullable NSString *)path
{
    if(path)
    {
        // Load the source image
        NSURL* url = [NSURL fileURLWithPath:path];
        
        if(url)
        {
            [self _initSurfaceWithURL:url];
        } // if
    } // if
} // _initSurfaceWithFile
 
- (void) _initIOSurface2DWithResource:(nullable NSString *)name
{
    if(name)
    {
        // Acquire the absolute pathname to the image resource in application's bundle
        NSBundle* bundle   = [NSBundle mainBundle];
        NSString* resource = [bundle resourcePath];
        NSString* path     = [NSString stringWithFormat:@"%@/%@", resource, name];
        
        [self _initSurfaceWithFile:path];
    } // if
} // initTextureWithResource
 
// Initialize instance variables
- (void) _initialize
{
    _bytesPerPixel    = 0;
    _samplesPerPixel  = 0;
    _bytesPerRow      = 0;
    _size             = 0;
    _bitmapInfo       = 0;
    _byteOrder        = 0;
    _format           = 0;
    _width            = 1920;
    _height           = 1080;
    _bitsPerPixel     = 32;
    _bitsPerComponent = 8;
    _alphaInfo        = kCGImageAlphaPremultipliedLast;
    _surface          = nullptr;
    _avoidSync        = YES;
    _isRGBA           = YES;
    
    isFinalized = NO;
    seed        = 0;
    options     = 0;
} // _initialize
 
// Default initializer to create a 2D I/O surface
- (nullable instancetype) init
{
    self = [super init];
    
    if(self)
    {
        [self _initialize];
    } // if
    
    return self;
} // init
 
+ (nullable instancetype) surface
{
    return [[[IOSurface2D allocWithZone:[self zone]] init] autorelease];
} // surfaceWithImage
 
// Designated initializer to create a 2D I/O surface from an image.
- (nullable instancetype) initWithImage:(nullable NSImage *)image
{
    self = [super init];
    
    if(self)
    {
        [self _initialize];
        [self _initSurfaceWithImage:image];
    } // if
    
    return self;
} // initWithImage
 
+ (nullable instancetype) surfaceWithImage:(nullable NSImage *)image
{
    return [[[IOSurface2D allocWithZone:[self zone]] initWithImage:image] autorelease];
} // surfaceWithImage
 
// Designated initializer to create a 2D I/O surface from an image
- (nullable instancetype) initWithImageRef:(nullable CGImageRef)image
{
    self = [super init];
    
    if(self)
    {
        [self _initialize];
        [self _drawImage:image];
    } // if
    
    return self;
} // initWithImageRef
 
+ (nullable instancetype) surfaceWithImageRef:(nullable CGImageRef)image
{
    return [[[IOSurface2D allocWithZone:[self zone]] initWithImageRef:image] autorelease];
} // surfaceWithImageRef
 
// Designated initializer to create a 2D I/O surface from an image file located at a URL
- (nullable instancetype) initWithURL:(nullable NSURL *)url
{
    self = [super init];
    
    if(self)
    {
        [self _initialize];
        [self _initSurfaceWithURL:url];
    } // if
    
    return self;
} // initWithURL
 
+ (nullable instancetype) surfaceWithURL:(nullable NSURL *)url
{
    return [[[IOSurface2D allocWithZone:[self zone]] initWithURL:url] autorelease];
} // surfaceWithURL
 
// Designated initializer to create a 2D I/O surface from an image file located at an absolute path
- (nullable instancetype) initWithFile:(nullable NSString *)path
{
    self = [super init];
    
    if(self)
    {
        [self _initialize];
        [self _initSurfaceWithFile:path];
    } // if
    
    return self;
} // initWithFile
 
+ (nullable instancetype) surfaceWithFile:(nullable NSString *)path
{
    return [[[IOSurface2D allocWithZone:[self zone]] initWithFile:path] autorelease];
} // surfaceWithFile
 
// Designated initializer to create a 2D I/O surface from an image file in application's bundle
- (nullable instancetype) initWithResource:(nullable NSString *)name
{
    self = [super init];
    
    if(self)
    {
        [self _initialize];
        [self _initIOSurface2DWithResource:name];
    } // if
    
    return self;
} // initWithResource
 
+ (nullable instancetype) surfaceWithResource:(nullable NSString *)name
{
    return [[[IOSurface2D allocWithZone:[self zone]] initWithResource:name] autorelease];
} // surfaceWithResource
 
// Destructor
- (void) dealloc
{
    if(_surface != nullptr)
    {
        CFRelease(_surface);
    } // if
    
    [super dealloc];
} // dealloc
 
- (nonnull instancetype) copyWithZone:(NSZone *)zone
{
    IOSurface2D* pSurface = [IOSurface2D new];
    
    if(pSurface)
    {
        pSurface.avoidSync        = _avoidSync;
        pSurface.bitsPerComponent = _bitsPerComponent;
        pSurface.bitsPerPixel     = _bitsPerPixel;
        pSurface.alphaInfo        = _alphaInfo;
        pSurface.width            = _width;
        pSurface.height           = _height;
        
        if([pSurface acquire])
        {
            [pSurface copy:_surface];
        } // if
    } // if
    
    return pSurface;
} // copyWithZone
 
// I/O surface bits-per-component
- (void) setBitsPerComponent:(size_t)bitsPerComponent
{
    if(!isFinalized)
    {
        _bitsPerComponent = bitsPerComponent;
    } // if
} // setBitsPerSample
 
// I/O surface bits-per-pixel
- (void) setBitsPerPixel:(size_t)bitsPerPixel
{
    if(!isFinalized)
    {
        _bitsPerPixel = bitsPerPixel;
    } // if
} // setSamplesPerPixel
 
// I/O surface alpha info
- (void) setAlphaInfo:(CGImageAlphaInfo)alphaInfo
{
    if(!isFinalized)
    {
        _alphaInfo = alphaInfo;
    } // if
} // setAlphaInfo
 
// I/O surface width
- (void) setWidth:(size_t)width
{
    if(!isFinalized)
    {
        _width = width;
    } // if
} // setWidth
 
// I/O surface height
- (void) setHeight:(size_t)height
{
    if(!isFinalized)
    {
        _height = height;
    } // if
} // setHeight
 
// Create a new I/O surface if the properties were set and default
// initializer was used to instantiate the object
- (BOOL) acquire
{
    BOOL success = _surface == nullptr;
    
    if(success)
    {
        success = [self _initSurfaceProperties];
        
        if(success)
        {
            _surface = [self _newSurface];
            
            isFinalized = _surface != nullptr;
        } // if
    } // if
    
    return success;
} // acquire
 
// Update the I/O surface data.  Does not require locking or unlocking.
// Also, note that the source data properties used here for write must
// match the created i/o surface.  You can not use this method to copy
// RGB to RGBA image.
- (BOOL) update:(nullable const uint8_t *)pBaseAddrSrc
{
    BOOL success = (pBaseAddrSrc != nullptr) && isFinalized;
    
    if(success)
    {
        IOReturn result = IOSurfaceLock(_surface, 0, &seed);
        
        success = result == kIOReturnSuccess;
        
        if(success)
        {
            uint8_t* pBaseAddrDst = static_cast<uint8_t *>(IOSurfaceGetBaseAddressOfPlane(_surface, 0));
            
            success = pBaseAddrDst != nullptr;
            
            if(success)
            {
                CG::memcpy(_size, pBaseAddrSrc, pBaseAddrDst);
            } // if
            
            IOSurfaceUnlock(_surface, 0, &seed);
        } // if
    } // if
    
    return success;
} // update
 
// The copy methods below do not require locking or unlocking.
// Also, note that the source i/o surface properties used here
// for copy must match the created i/o surface.  Furthermore,
// you can not use this method to copy RGB to RGBA i/o surface.
 
// Make a copy of an I/O surface image in a plane with index 0.
- (BOOL) copy:(nullable const IOSurfaceRef)surface
{
    IOReturn nResult = kIOReturnError;
    
    CG::s2dcpy(0, surface, 0, _surface, nResult);
    
    return nResult == kIOReturnSuccess;
} // copy
 
// Make a copy of an I/O surface image in a plane at an index.
- (BOOL) copy:(nullable const IOSurfaceRef)surface
        index:(const size_t)index
{
    IOReturn nResult = kIOReturnError;
    
    CG::s2dcpy(index, surface, 0, _surface, nResult);
    
    return nResult == kIOReturnSuccess;
} // copy
 
// Map the base address of the I/O surface for reading data
- (nullable uint8_t *) map
{
    uint8_t* pBaseAddr = nullptr;
    
    if(isFinalized)
    {
        pBaseAddr = static_cast<uint8_t *>(IOSurfaceGetBaseAddress(_surface));
    } // if
    
    return pBaseAddr;
} // map
 
// Map the base address of the I/O surface for reading/writing data
- (nullable uint8_t *) map:(const NSPoint)point
{
    uint8_t* ptr = nullptr;
    
    if(isFinalized)
    {        
        uint8_t* base = static_cast<uint8_t *>(IOSurfaceGetBaseAddressOfPlane(_surface, 0));
        
        const size_t width  = size_t(point.x) * _bytesPerPixel;
        const size_t height = size_t(point.y) * _bytesPerRow;
        const size_t offset = width + height;
        
        ptr = base + offset;
    } // if
    
    return ptr;
} // map
 
// Lock the I/O surface before mapping to read or write
- (BOOL) lock:(const BOOL)isReadOnly
{
    IOReturn result = kIOReturnError;
    
    if(isFinalized)
    {
        options = (isReadOnly) ? (kIOSurfaceLockReadOnly | ((_avoidSync) ? kIOSurfaceLockAvoidSync : 0)) : 0;
        
        result = IOSurfaceLock(_surface, options, &seed);
    } // if
    
    return result == kIOReturnSuccess;
} // lock
 
// Unlock the I/O surface after a mapped read or write
- (BOOL) unlock
{
    IOReturn result = kIOReturnError;
    
    if(isFinalized)
    {
        result = IOSurfaceUnlock(_surface, options, &seed);
    } // if
    
    return result == kIOReturnSuccess;
} // unlock
 
@end