Workaround for CIImage to NSImage memory issue
NetNewsWire 3 uses Core Image to do a few things, most noticeably to tint the feed and folder icons to match the subscriptions list background.
Core Image, as I was pleased to discover, makes doing stuff like this really, really easy, and it’s fun to use. It takes just a few lines of code to run a filter on an image.
Now, the only slightly tricky part is I’m working with NSImage everywhere in NetNewsWire, but Core Image uses CIImage. So I have to take an NSImage, create a CIImage based on it, run a filter on the CIImage (the tinting or whatever)—and then create an NSImage from the altered CIImage. (The NSImage is what gets displayed.)
I’ve been working on NetNewsWire’s memory use by using leaks, ObjectAlloc, and MallocDebug. And I saw that my CIImage to NSImage conversion was using a lot of memory. It wasn’t reported as a leak but as an allocation by MallocDebug—and I saw that memory use kept going up and up when I triggered it.
I discovered that creating an NSImage from a CIImage—if the CIImage has had a filter run on it—can cause unnecessary memory use on some systems. On my development machine, for instance, but not on the PowerBook sitting next to me.
So... after a couple days of doing a bunch of research, emailing with people, pulling hair, reporting a bug to Apple (rdar://5046037), getting help reporting a bug to Apple, complaining on Twitter, complaining to the private testers, explaining the issue to my cat, I finally found a workaround.
Here’s an app (with source) that shows the leak.
What it does is create an NSImage from a CIImage over and over. You can watch the app’s memory use go up in top or Activity Monitor. (Unless it doesn’t, because it doesn’t leak on every system.)
It also includes the workaround, which is, in a nutshell, to use the software renderer, rather than the GPU.
For any case where performance is an issue, this workaround isn’t usable. But in my case it’s fine.
The leaking code (re-formatted for the web) looks like this:
NSImage *image = [[[NSImage alloc] initWithSize: NSMakeSize([ciImage extent].size.width, [ciImage extent].size.height)] autorelease]; [image lockFocus]; [[[NSGraphicsContext currentContext] CIContext] drawImage:ciImage atPoint:CGPointMake(0, 0) fromRect:[ciImage extent]]; /*This appears to be the line that leaks*/ [image unlockFocus];
The non-leaking code looks like this:
NSImage *image = [[[NSImage alloc] initWithSize: NSMakeSize([ciImage extent].size.width, [ciImage extent].size.height)] autorelease]; [image lockFocus]; CGContextRef contextRef = [[NSGraphicsContext currentContext] graphicsPort]; CIContext *ciContext = [CIContext contextWithCGContext:contextRef options:[NSDictionary dictionaryWithObject: [NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]]; [ciContext drawImage:ciImage atPoint:CGPointMake(0, 0) fromRect:[ciImage extent]]; /*Does not leak when using the software renderer!*/ [image unlockFocus];
My hope, of course, is that this workaround is needed temporarily, that it will get fixed. Obviously, using the software renderer defeats much of what’s cool about Core Image. But, for now, hey, cool, no leak, at least.