GPUImageFramebuffer.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. #import "GPUImageFramebuffer.h"
  2. #import "GPUImageOutput.h"
  3. @interface GPUImageFramebuffer()
  4. {
  5. GLuint framebuffer;
  6. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  7. CVPixelBufferRef renderTarget;
  8. CVOpenGLESTextureRef renderTexture;
  9. NSUInteger readLockCount;
  10. #else
  11. #endif
  12. NSUInteger framebufferReferenceCount;
  13. BOOL referenceCountingDisabled;
  14. }
  15. - (void)generateFramebuffer;
  16. - (void)generateTexture;
  17. - (void)destroyFramebuffer;
  18. @end
  19. void dataProviderReleaseCallback (void *info, const void *data, size_t size);
  20. void dataProviderUnlockCallback (void *info, const void *data, size_t size);
  21. @implementation GPUImageFramebuffer
  22. @synthesize size = _size;
  23. @synthesize textureOptions = _textureOptions;
  24. @synthesize texture = _texture;
  25. @synthesize missingFramebuffer = _missingFramebuffer;
  26. #pragma mark -
  27. #pragma mark Initialization and teardown
  28. - (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;
  29. {
  30. if (!(self = [super init]))
  31. {
  32. return nil;
  33. }
  34. _textureOptions = fboTextureOptions;
  35. _size = framebufferSize;
  36. framebufferReferenceCount = 0;
  37. referenceCountingDisabled = NO;
  38. _missingFramebuffer = onlyGenerateTexture;
  39. if (_missingFramebuffer)
  40. {
  41. runSynchronouslyOnVideoProcessingQueue(^{
  42. [GPUImageContext useImageProcessingContext];
  43. [self generateTexture];
  44. framebuffer = 0;
  45. });
  46. }
  47. else
  48. {
  49. [self generateFramebuffer];
  50. }
  51. return self;
  52. }
  53. - (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;
  54. {
  55. if (!(self = [super init]))
  56. {
  57. return nil;
  58. }
  59. GPUTextureOptions defaultTextureOptions;
  60. defaultTextureOptions.minFilter = GL_LINEAR;
  61. defaultTextureOptions.magFilter = GL_LINEAR;
  62. defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
  63. defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
  64. defaultTextureOptions.internalFormat = GL_RGBA;
  65. defaultTextureOptions.format = GL_BGRA;
  66. defaultTextureOptions.type = GL_UNSIGNED_BYTE;
  67. _textureOptions = defaultTextureOptions;
  68. _size = framebufferSize;
  69. framebufferReferenceCount = 0;
  70. referenceCountingDisabled = YES;
  71. _texture = inputTexture;
  72. return self;
  73. }
  74. - (id)initWithSize:(CGSize)framebufferSize;
  75. {
  76. GPUTextureOptions defaultTextureOptions;
  77. defaultTextureOptions.minFilter = GL_LINEAR;
  78. defaultTextureOptions.magFilter = GL_LINEAR;
  79. defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
  80. defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
  81. defaultTextureOptions.internalFormat = GL_RGBA;
  82. defaultTextureOptions.format = GL_BGRA;
  83. defaultTextureOptions.type = GL_UNSIGNED_BYTE;
  84. if (!(self = [self initWithSize:framebufferSize textureOptions:defaultTextureOptions onlyTexture:NO]))
  85. {
  86. return nil;
  87. }
  88. return self;
  89. }
  90. - (void)dealloc
  91. {
  92. [self destroyFramebuffer];
  93. }
  94. #pragma mark -
  95. #pragma mark Internal
  96. - (void)generateTexture;
  97. {
  98. glActiveTexture(GL_TEXTURE1);
  99. glGenTextures(1, &_texture);
  100. glBindTexture(GL_TEXTURE_2D, _texture);
  101. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _textureOptions.minFilter);
  102. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _textureOptions.magFilter);
  103. // This is necessary for non-power-of-two textures
  104. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);
  105. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);
  106. // TODO: Handle mipmaps
  107. }
  108. - (void)generateFramebuffer;
  109. {
  110. runSynchronouslyOnVideoProcessingQueue(^{
  111. [GPUImageContext useImageProcessingContext];
  112. glGenFramebuffers(1, &framebuffer);
  113. glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
  114. // By default, all framebuffers on iOS 5.0+ devices are backed by texture caches, using one shared cache
  115. if ([GPUImageContext supportsFastTextureUpload])
  116. {
  117. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  118. CVOpenGLESTextureCacheRef coreVideoTextureCache = [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache];
  119. // Code originally sourced from http://allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/
  120. CFDictionaryRef empty; // empty value for attr value.
  121. CFMutableDictionaryRef attrs;
  122. empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // our empty IOSurface properties dictionary
  123. attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  124. CFDictionarySetValue(attrs, kCVPixelBufferIOSurfacePropertiesKey, empty);
  125. CVReturn err = CVPixelBufferCreate(kCFAllocatorDefault, (int)_size.width, (int)_size.height, kCVPixelFormatType_32BGRA, attrs, &renderTarget);
  126. if (err)
  127. {
  128. NSLog(@"FBO size: %f, %f", _size.width, _size.height);
  129. NSAssert(NO, @"Error at CVPixelBufferCreate %d", err);
  130. }
  131. err = CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,
  132. NULL, // texture attributes
  133. GL_TEXTURE_2D,
  134. _textureOptions.internalFormat, // opengl format
  135. (int)_size.width,
  136. (int)_size.height,
  137. _textureOptions.format, // native iOS format
  138. _textureOptions.type,
  139. 0,
  140. &renderTexture);
  141. if (err)
  142. {
  143. NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
  144. }
  145. CFRelease(attrs);
  146. CFRelease(empty);
  147. glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
  148. _texture = CVOpenGLESTextureGetName(renderTexture);
  149. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);
  150. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);
  151. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);
  152. #endif
  153. }
  154. else
  155. {
  156. [self generateTexture];
  157. glBindTexture(GL_TEXTURE_2D, _texture);
  158. glTexImage2D(GL_TEXTURE_2D, 0, _textureOptions.internalFormat, (int)_size.width, (int)_size.height, 0, _textureOptions.format, _textureOptions.type, 0);
  159. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
  160. }
  161. #ifndef NS_BLOCK_ASSERTIONS
  162. GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  163. NSAssert(status == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", status);
  164. #endif
  165. glBindTexture(GL_TEXTURE_2D, 0);
  166. });
  167. }
  168. - (void)destroyFramebuffer;
  169. {
  170. runSynchronouslyOnVideoProcessingQueue(^{
  171. [GPUImageContext useImageProcessingContext];
  172. if (framebuffer)
  173. {
  174. glDeleteFramebuffers(1, &framebuffer);
  175. framebuffer = 0;
  176. }
  177. if ([GPUImageContext supportsFastTextureUpload] && (!_missingFramebuffer))
  178. {
  179. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  180. if (renderTarget)
  181. {
  182. CFRelease(renderTarget);
  183. renderTarget = NULL;
  184. }
  185. if (renderTexture)
  186. {
  187. CFRelease(renderTexture);
  188. renderTexture = NULL;
  189. }
  190. #endif
  191. }
  192. else
  193. {
  194. glDeleteTextures(1, &_texture);
  195. }
  196. });
  197. }
  198. #pragma mark -
  199. #pragma mark Usage
  200. - (void)activateFramebuffer;
  201. {
  202. glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
  203. glViewport(0, 0, (int)_size.width, (int)_size.height);
  204. }
  205. #pragma mark -
  206. #pragma mark Reference counting
  207. - (void)lock;
  208. {
  209. if (referenceCountingDisabled)
  210. {
  211. return;
  212. }
  213. framebufferReferenceCount++;
  214. }
  215. - (void)unlock;
  216. {
  217. if (referenceCountingDisabled)
  218. {
  219. return;
  220. }
  221. NSAssert(framebufferReferenceCount > 0, @"Tried to overrelease a framebuffer, did you forget to call -useNextFrameForImageCapture before using -imageFromCurrentFramebuffer?");
  222. framebufferReferenceCount--;
  223. if (framebufferReferenceCount < 1)
  224. {
  225. [[GPUImageContext sharedFramebufferCache] returnFramebufferToCache:self];
  226. }
  227. }
  228. - (void)clearAllLocks;
  229. {
  230. framebufferReferenceCount = 0;
  231. }
  232. - (void)disableReferenceCounting;
  233. {
  234. referenceCountingDisabled = YES;
  235. }
  236. - (void)enableReferenceCounting;
  237. {
  238. referenceCountingDisabled = NO;
  239. }
  240. #pragma mark -
  241. #pragma mark Image capture
  242. void dataProviderReleaseCallback (void *info, const void *data, size_t size)
  243. {
  244. free((void *)data);
  245. }
  246. void dataProviderUnlockCallback (void *info, const void *data, size_t size)
  247. {
  248. GPUImageFramebuffer *framebuffer = (__bridge_transfer GPUImageFramebuffer*)info;
  249. [framebuffer restoreRenderTarget];
  250. [framebuffer unlock];
  251. [[GPUImageContext sharedFramebufferCache] removeFramebufferFromActiveImageCaptureList:framebuffer];
  252. }
  253. - (CGImageRef)newCGImageFromFramebufferContents;
  254. {
  255. // a CGImage can only be created from a 'normal' color texture
  256. NSAssert(self.textureOptions.internalFormat == GL_RGBA, @"For conversion to a CGImage the output texture format for this filter must be GL_RGBA.");
  257. NSAssert(self.textureOptions.type == GL_UNSIGNED_BYTE, @"For conversion to a CGImage the type of the output texture of this filter must be GL_UNSIGNED_BYTE.");
  258. __block CGImageRef cgImageFromBytes;
  259. runSynchronouslyOnVideoProcessingQueue(^{
  260. [GPUImageContext useImageProcessingContext];
  261. NSUInteger totalBytesForImage = (int)_size.width * (int)_size.height * 4;
  262. // It appears that the width of a texture must be padded out to be a multiple of 8 (32 bytes) if reading from it using a texture cache
  263. GLubyte *rawImagePixels;
  264. CGDataProviderRef dataProvider = NULL;
  265. if ([GPUImageContext supportsFastTextureUpload])
  266. {
  267. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  268. NSUInteger paddedWidthOfImage = CVPixelBufferGetBytesPerRow(renderTarget) / 4.0;
  269. NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4;
  270. glFinish();
  271. CFRetain(renderTarget); // I need to retain the pixel buffer here and release in the data source callback to prevent its bytes from being prematurely deallocated during a photo write operation
  272. [self lockForReading];
  273. rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget);
  274. dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback);
  275. [[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self]; // In case the framebuffer is swapped out on the filter, need to have a strong reference to it somewhere for it to hang on while the image is in existence
  276. #else
  277. #endif
  278. }
  279. else
  280. {
  281. [self activateFramebuffer];
  282. rawImagePixels = (GLubyte *)malloc(totalBytesForImage);
  283. glReadPixels(0, 0, (int)_size.width, (int)_size.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels);
  284. dataProvider = CGDataProviderCreateWithData(NULL, rawImagePixels, totalBytesForImage, dataProviderReleaseCallback);
  285. [self unlock]; // Don't need to keep this around anymore
  286. }
  287. CGColorSpaceRef defaultRGBColorSpace = CGColorSpaceCreateDeviceRGB();
  288. if ([GPUImageContext supportsFastTextureUpload])
  289. {
  290. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  291. cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), defaultRGBColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, NO, kCGRenderingIntentDefault);
  292. #else
  293. #endif
  294. }
  295. else
  296. {
  297. cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, 4 * (int)_size.width, defaultRGBColorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaLast, dataProvider, NULL, NO, kCGRenderingIntentDefault);
  298. }
  299. // Capture image with current device orientation
  300. CGDataProviderRelease(dataProvider);
  301. CGColorSpaceRelease(defaultRGBColorSpace);
  302. });
  303. return cgImageFromBytes;
  304. }
  305. - (void)restoreRenderTarget;
  306. {
  307. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  308. [self unlockAfterReading];
  309. CFRelease(renderTarget);
  310. #else
  311. #endif
  312. }
  313. #pragma mark -
  314. #pragma mark Raw data bytes
  315. - (void)lockForReading
  316. {
  317. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  318. if ([GPUImageContext supportsFastTextureUpload])
  319. {
  320. if (readLockCount == 0)
  321. {
  322. CVPixelBufferLockBaseAddress(renderTarget, 0);
  323. }
  324. readLockCount++;
  325. }
  326. #endif
  327. }
  328. - (void)unlockAfterReading
  329. {
  330. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  331. if ([GPUImageContext supportsFastTextureUpload])
  332. {
  333. NSAssert(readLockCount > 0, @"Unbalanced call to -[GPUImageFramebuffer unlockAfterReading]");
  334. readLockCount--;
  335. if (readLockCount == 0)
  336. {
  337. CVPixelBufferUnlockBaseAddress(renderTarget, 0);
  338. }
  339. }
  340. #endif
  341. }
  342. - (NSUInteger)bytesPerRow;
  343. {
  344. if ([GPUImageContext supportsFastTextureUpload])
  345. {
  346. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  347. return CVPixelBufferGetBytesPerRow(renderTarget);
  348. #else
  349. return _size.width * 4; // TODO: do more with this on the non-texture-cache side
  350. #endif
  351. }
  352. else
  353. {
  354. return _size.width * 4;
  355. }
  356. }
  357. - (GLubyte *)byteBuffer;
  358. {
  359. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  360. [self lockForReading];
  361. GLubyte * bufferBytes = CVPixelBufferGetBaseAddress(renderTarget);
  362. [self unlockAfterReading];
  363. return bufferBytes;
  364. #else
  365. return NULL; // TODO: do more with this on the non-texture-cache side
  366. #endif
  367. }
  368. - (GLuint)texture;
  369. {
  370. // NSLog(@"Accessing texture: %d from FB: %@", _texture, self);
  371. return _texture;
  372. }
  373. @end