Reputation: 14886
I'm trying to optimize some image filtering by using a singleton object that contains the image filter for me to use anywhere. I am running into an issue where I believe I am calling the filter several times at once (several cells are displayed, after downloading image, before display, I am processing the image with a filter, which may occur pretty much at the same time).
I run into this issue:
NSAssert(framebufferReferenceCount > 0, @"Tried to overrelease a framebuffer, did you forget to call -useNextFrameForImageCapture before using -imageFromCurrentFramebuffer?");
https://github.com/BradLarson/GPUImage/blob/master/framework/Source/GPUImageFramebuffer.m#L269
Here's my code:
Interface:
#import <GPUImage/GPUImage.h>
@interface GPUImageFilterManager : NSObject
+ (GPUImageFilterManager*)sharedInstance;
@property (nonatomic, strong) GPUImageiOSBlurFilter *blurFilter;
@property (nonatomic, strong) GPUImageLuminanceThresholdFilter *luminanceFilter;
@end
Implementation:
#import "GPUImageFilterManager.h"
@implementation GPUImageFilterManager
+ (GPUImageFilterManager*)sharedInstance {
static GPUImageFilterManager *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [GPUImageFilterManager new];
});
return _sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.luminanceFilter = [[GPUImageLuminanceThresholdFilter alloc] init];
self.luminanceFilter.threshold = 0.5;
self.blurFilter = [[GPUImageiOSBlurFilter alloc] init];
[self.blurFilter setBlurRadiusInPixels:BLUR_RADIUS];
[self.blurFilter setSaturation:SATURATION];
[self.blurFilter setDownsampling:DOWNSAMPLING];
[self.blurFilter setRangeReductionFactor:RANGEREDUCTION];
}
return self;
}
-(GPUImageiOSBlurFilter*)blurFilter {
[_blurFilter useNextFrameForImageCapture];
return _blurFilter;
}
-(GPUImageLuminanceThresholdFilter*)luminanceFilter {
[_luminanceFilter useNextFrameForImageCapture];
return _luminanceFilter;
}
And in my code I call:
[[[GPUImageFilterManager sharedInstance] blurFilter] imageByFilteringImage:image];
Previously, I had a GPUImageLuminanceThresholdFilter property in each cell and I wanted to optimize this to just use a single instance of it, but it now seems I won't be able to process multiple images at once. Any ideas or advice here?
Upvotes: 0
Views: 1424
Reputation: 170319
My guess about what's going wrong here is that -imageByFilteringImage:
isn't really an atomic operation, and if you're triggering it on multiple threads at the same time, you're going to get bizarre behavior. Take a look at what happens inside that method (via the -newCGImageByFilteringCGImage:
method):
- (CGImageRef)newCGImageByFilteringCGImage:(CGImageRef)imageToFilter;
{
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithCGImage:imageToFilter];
[self useNextFrameForImageCapture];
[stillImageSource addTarget:(id<GPUImageInput>)self];
[stillImageSource processImage];
CGImageRef processedImage = [self newCGImageFromCurrentlyProcessedOutput];
[stillImageSource removeTarget:(id<GPUImageInput>)self];
return processedImage;
}
A GPUImagePicture is created, attached to the filter, processed, and the output extracted. If you're using the same filter across multiple threads, and you call this method, odds are that you're going to interrupt this at various points and create bizarre filtering pathways. Thus the error above (as well as other potential crashes and image corruption).
If you want to use this filter in a singleton, my recommendation would be to set up a serial dispatch queue and wrap accessed to that filter in dispatches to that queue. In particular, I'd wrap synchronous dispatches to this queue around your -imageByFilteringImage:
above to guarantee that this always fully executes and returns an image before being triggered again by a different image and thread.
The GPUImage OpenGL ES context can only perform a single rendering operation at a time, so you don't lose much by limiting access to the filter to only one thread at a time. It's also pretty simple to implement, and I use this pattern of limiting access to shared resources via GCD queues throughout the framework.
Upvotes: 5