Memory leak on CIContext createCGImage at iOS 9?

Hello,


I'm seeing this on a Swift 2 app compiled with Xcode 7 GM running on iOS 9 GM. I believe the same behaviour is seen on Swift 1.2 apps compiled for iOS 8 when running on iOS 9 (the behaviour does not reproduce on iOS 8, though).


The leak seems to be external to my application as the Memory Report on Xcode shows the memory for "Other Processes" growing to the point where there is a Memory Warning and the application is terminated. The memory of the App itself remains stable. I have not been able to detect any leak using Instruments.


I have been commenting out parts of my code and it seems that the leak happens everytime this line is called:

let outputCGImage = self.ciContext.createCGImage(output, fromRect: input.extent)

I have tried a few things to prevent the leak, with no success:

1.- I'm not able to do CGImageRelease of outputCGImage as those objects are automatically managed.

2.- I have tried to call that line on an autoreleasepool block.

3.- I have tried to call that line from the Main Thread (originally it is called on a background thread).

4.- I have tried to destroy and recreate ciContext hoping to release the memory leaked.

Nothing has helped.


Any ideas about what is going here? I really doubt this is a bug on iOS 9 GM as I believe it is very noticeable, but I can't really tell what is wrong with my code.


Additionally any ideas on how to properly debug this kind of external leaks would be appreciated too.


Thanks a lot!


UPDATE: Destroying and recreating the EAGLContext used with the CIContext brings back the memory. Obviously this is not a solution as re-creating the context everytime a render is needed is very expensive, but I think it gives a hint on where the problem might be.

Answered by ikono in 66901022

It looks like they fixed this in 9.1b3. If anyone needs a workaround that works on iOS 9.0.x, I was able to get it working with this:


extension CIContext {
    func createCGImage_(image:CIImage, fromRect:CGRect) -> CGImage {
        let width = Int(fromRect.width)
        let height = Int(fromRect.height)
       
        let rawData =  UnsafeMutablePointer<UInt8>.alloc(width * height * 4)
        render(image, toBitmap: rawData, rowBytes: width * 4, bounds: fromRect, format: kCIFormatRGBA8, colorSpace: CGColorSpaceCreateDeviceRGB())
        let dataProvider = CGDataProviderCreateWithData(nil, rawData, height * width * 4) {info, data, size in UnsafeMutablePointer<UInt8>(data).dealloc(size)}
        return CGImageCreate(width, height, 8, 32, width * 4, CGColorSpaceCreateDeviceRGB(), CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue), dataProvider, nil, false, .RenderingIntentDefault)!
    }
}

Have you tried a toy app in objc and a toy app in swift to see if they do the same thing?


I noticed that CIGuassianBlur effect is way slower on iOS 9 than iOS 8. But I only have tried it using swift.


Hard to imagine swift would be related to the issue, though, since we're not actually using swift to do anything other than call out to an API.

Thanks for your reply.


I have created a new simple project and I'm able to reproduce the issue. I have started believing that this is actually a bug. I have opened radar 22644754 for it. I'm concerned about how long it might take to get attention from Apple as I'm really not able to find a workaround for it.


If anyone is interested in the sample project, you can get it from here: https://www.dropbox.com/s/zm19u8rmujv6jet/EAGLContextLeakDemo.zip?dl=0


The steps to reproduce are:


1. Open and run the attached project on a Device.

2. Observe the "Memory Report" screen on Xcode. The growth will be seen on the "Other Processes" part of the "Usage Comparison" pie chart.

3. The app presents three buttons. Each of them will execute the createCGImage command a certain number of times (shown on the button labels).

4. Tapping on any of the buttons will result on a memory usage increase of the "Other Processes". This might be more noticeable after performing several createCGImage.

5. Tapping on the 100 Renders buttons will show more clearly the effect. 6. When the memory growth is excessive, the App will crash.


About the CIGaussianBlur perfomance hit, I will do some testing later today (or more likely tomorrow) and I will let you know if I find anything interesting.

I have a similar issue with the iOS9 GM - doesn't matter how I change my code, it will crash after a couple of calls on the iPhone 6. Most annoyingly, this doesn't happen on the first generation iPad mini.


[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
    {
        if (error) return;

        __block NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"ipdf_pic_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
        dispatch_async(dispatch_get_main_queue(), ^
        {
   
            @autoreleasepool
            {
                CIImage *enhancedImage = [CIImage imageWithData:imageData];
         
                if (!enhancedImage) return;
         
                static CIContext *ctx = nil; if (!ctx) ctx = [CIContext contextWithOptions:nil];
         
                CGImageRef imageRef = [ctx createCGImage:enhancedImage fromRect:enhancedImage.extent format:kCIFormatBGRA8 colorSpace:nil];
         
                UIImage *image = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationRight];
         
                [[NSFileManager defaultManager] createFileAtPath:filePath contents:UIImageJPEGRepresentation(image, 0.8) attributes:nil];
         
                CGImageRelease(imageRef);
            }
        });
    }];


I think the culprid lies deep within the memory allocation of CIContext, esp. createCFData, see attached Screenshot: http://i.imgur.com/rfnKx7t.png

Hi,


I had the same issue. I solved it by defining the CIcontext in the method. This way it nilified at every cycle.


Hope this helps.

Hello,


I had the same issue in Objective-C no ARC.

Memory leak occurs useing createCGImage, CGImageRelease with iOS9GM.

But but not occurs with iOS8 or iOS7.

I think some bugs are in iOS9GM.


You can download sample app from "http://www.osamu.co.jp/DataArea/VideoCameraTest.zip".

Case1

Please tap "Start(createCGImage)".

In this case createCGImage, CGImageRelease is used.

And memory leak occurs.

Please tap "Stop".

Case 2

Please tap "Start".

In this case createCGImage, CGImageRelease is not used.

And memory leak do not occurs.

Please tap "Stop".

Same issue. It is also present in iOS 9 release version.😟

iOS 8 is ok. Probaby it's iOS 9 bug, and we can do nothing with it.. But Appl's apps (camera, edit photo) still works ok.

I had the same problem (ok with ios8.x, unexplainable leak on ios9). Solved by using CIContext with MTLDevice (had to drop EAGLContext).

Accepted Answer

It looks like they fixed this in 9.1b3. If anyone needs a workaround that works on iOS 9.0.x, I was able to get it working with this:


extension CIContext {
    func createCGImage_(image:CIImage, fromRect:CGRect) -> CGImage {
        let width = Int(fromRect.width)
        let height = Int(fromRect.height)
       
        let rawData =  UnsafeMutablePointer<UInt8>.alloc(width * height * 4)
        render(image, toBitmap: rawData, rowBytes: width * 4, bounds: fromRect, format: kCIFormatRGBA8, colorSpace: CGColorSpaceCreateDeviceRGB())
        let dataProvider = CGDataProviderCreateWithData(nil, rawData, height * width * 4) {info, data, size in UnsafeMutablePointer<UInt8>(data).dealloc(size)}
        return CGImageCreate(width, height, 8, 32, width * 4, CGColorSpaceCreateDeviceRGB(), CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue), dataProvider, nil, false, .RenderingIntentDefault)!
    }
}

Right, I can confirm issue has been resolved as of 9.1 Beta 3.

So, will it take long for 9.1 to appear? Does anyone know when they plan to release it, and how it usually happens, I've never paid attention to this.

I get the same memory leak on iOS 9.2. I've tested dropping EAGLContext by using MetalKit and MLKDevice. I've tested using different methods of CIContext like drawImage, createCGImage and render but nothing seem to work.


It is very clear that this is a bug as of iOS 9. Try it out your self by downloading the example app from Apple (see below) and then run the same project on a device with iOS 8.4, then on a device with iOS 9.2 and pay attention to the memory gauge in Xcode.


Download

https://developer.apple.com/library/ios/samplecode/AVBasicVideoOutput/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013109


Add this to the APLEAGLView.h:20

@property (strong, nonatomic) CIContext* ciContext;


Replace APLEAGLView.m:118 with this

  [EAGLContext setCurrentContext:_context];
   _ciContext = [CIContext contextWithEAGLContext:_context];


And finaly replace APLEAGLView.m:341-343 with this

  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
   
    @autoreleasepool
    {
        CIImage* sourceImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
        CIFilter* filter = [CIFilter filterWithName:@"CIGaussianBlur" keysAndValues:kCIInputImageKey, sourceImage, nil];
        CIImage* filteredImage = filter.outputImage;
       
        [_ciContext render:filteredImage toCVPixelBuffer:pixelBuffer];
    }
   
  glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);

This did the trick for me. Thank you!

Objective-C version of your code


static const size_t kComponentsPerPixel = 4;
static const size_t kBitsPerComponent = sizeof(unsigned char) * 8;
static void releasePixels(void *info, const void *data, size_t size)
{
    free((void*)data);
}
- (UIImage *) imageFromCIImage:(CIImage *)img scale:(CGFloat)scale orientation:(UIImageOrientation)orientation
{
    int width = (int)img.extent.size.width;
    int height = (int)img.extent.size.height;
   
    long memsize = sizeof(unsigned char) * width * height * kComponentsPerPixel;
   
    unsigned char *rawData = malloc(memsize);
   
    CIContext *context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer: @NO}];
   
    CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
   
    [context render:img toBitmap:rawData rowBytes:width*kComponentsPerPixel bounds:img.extent format:kCIFormatRGBA8 colorSpace:rgb];
   
    CGDataProviderRef provider = CGDataProviderCreateWithData(nil, rawData, memsize, releasePixels);
   
    CGImageRef imageFromContext = CGImageCreate(width,
                                                height,
                                                kBitsPerComponent,
                                                kBitsPerComponent * kComponentsPerPixel,
                                                width*kComponentsPerPixel,
                                                rgb,
                                                kCGBitmapByteOrderDefault | kCGImageAlphaLast,
                                                provider,
                                                NULL,
                                                false,
                                                kCGRenderingIntentDefault);
    UIImage *outImage = [UIImage imageWithCGImage:imageFromContext scale:scale orientation:orientation];
   
    CGImageRelease(imageFromContext);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(rgb);
   
    return outImage;
}

We had the same problem, converting CIImage to CGImage using a GPU (EAGL) context in a dispatch queue. Huge leaks. This problem is all over google.

Just search for CIContext createCGImage: leak. There were a bunch of solutions proposed, but none worked in our case.


Viktorg's ObjC solution fixed it. We wanted a CGImageRef out, so I just returned his imageFromContext variable. Then the caller needs to release this with CGImageRelease.


It seems to be slower than -[CIContext

createCGImage:] but perhaps that can be improved as we go...

What we did for somewhat of a speedup on line 16 above, so the context isn't created each time:


static CIContext *context = nil;

if (!context) [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer: @NO}];


It seems to work faster w/o causing memory leaks. Of course you could put context as a containing class @property, but this was just a shortcut to check.

Memory leak on CIContext createCGImage at iOS 9?
 
 
Q