MGOpenGLView.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /*
  2. File: OpenGLPixelBufferView.m
  3. Abstract: The OpenGL ES view
  4. Version: 2.1
  5. Copyright (C) 2014 Apple Inc. All Rights Reserved.
  6. */
  7. #import "MGOpenGLView.h"
  8. #import <OpenGLES/EAGL.h>
  9. #import <QuartzCore/CAEAGLLayer.h>
  10. #import "GLESUtils.h"
  11. #if !defined(_STRINGIFY)
  12. #define __STRINGIFY( _x ) # _x
  13. #define _STRINGIFY( _x ) __STRINGIFY( _x )
  14. #endif
  15. @interface MGOpenGLView ()
  16. {
  17. EAGLContext *_oglContext;
  18. CVOpenGLESTextureCacheRef _textureCache;
  19. GLint _width;
  20. GLint _height;
  21. GLuint _frameBufferHandle;
  22. GLuint _colorBufferHandle;
  23. GLuint _program;
  24. GLint _frame;
  25. }
  26. @end
  27. @implementation MGOpenGLView
  28. - (void)dealloc
  29. {
  30. [self reset];
  31. }
  32. + (Class)layerClass
  33. {
  34. return [CAEAGLLayer class];
  35. }
  36. - (instancetype)initWithFrame:(CGRect)frame
  37. {
  38. self = [super initWithFrame:frame];
  39. if ( self )
  40. {
  41. // On iOS8 and later we use the native scale of the screen as our content scale factor.
  42. // This allows us to render to the exact pixel resolution of the screen which avoids additional scaling and GPU rendering work.
  43. // For example the iPhone 6 Plus appears to UIKit as a 736 x 414 pt screen with a 3x scale factor (2208 x 1242 virtual pixels).
  44. // But the native pixel dimensions are actually 1920 x 1080.
  45. // Since we are streaming 1080p buffers from the camera we can render to the iPhone 6 Plus screen at 1:1 with no additional scaling if we set everything up correctly.
  46. // Using the native scale of the screen also allows us to render at full quality when using the display zoom feature on iPhone 6/6 Plus.
  47. // Only try to compile this code if we are using the 8.0 or later SDK.
  48. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
  49. if ( [UIScreen instancesRespondToSelector:@selector(nativeScale)] )
  50. {
  51. self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
  52. }
  53. else
  54. #endif
  55. {
  56. self.contentScaleFactor = [UIScreen mainScreen].scale;
  57. }
  58. // Initialize OpenGL ES 2
  59. CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
  60. eaglLayer.opaque = YES;
  61. eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @(NO),
  62. kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8};
  63. _oglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
  64. if ( ! _oglContext ) {
  65. NSLog( @"Problem with OpenGL context." );
  66. return nil;
  67. }
  68. }
  69. return self;
  70. }
  71. double radians(float degrees) {
  72. return ( degrees * M_PI ) / 180.0;
  73. }
  74. - (BOOL)initializeBuffers
  75. {
  76. BOOL success = YES;
  77. glDisable( GL_DEPTH_TEST );
  78. glGenFramebuffers( 1, &_frameBufferHandle );
  79. glBindFramebuffer( GL_FRAMEBUFFER, _frameBufferHandle );
  80. glGenRenderbuffers( 1, &_colorBufferHandle );
  81. glBindRenderbuffer( GL_RENDERBUFFER, _colorBufferHandle );
  82. [_oglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
  83. glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_width );
  84. glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_height );
  85. glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBufferHandle );
  86. if ( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE ) {
  87. NSLog( @"Failure with framebuffer generation" );
  88. success = NO;
  89. goto bail;
  90. }
  91. // Create a new CVOpenGLESTexture cache
  92. CVReturn err = CVOpenGLESTextureCacheCreate( kCFAllocatorDefault, NULL, _oglContext, NULL, &_textureCache );
  93. if ( err ) {
  94. NSLog( @"Error at CVOpenGLESTextureCacheCreate %d", err );
  95. success = NO;
  96. goto bail;
  97. }
  98. [self setupVideoProgram];
  99. if ( ! _program ) {
  100. NSLog( @"Error creating the program" );
  101. success = NO;
  102. goto bail;
  103. }
  104. _frame = glueGetUniformLocation( _program, "videoframe" );
  105. bail:
  106. if ( ! success ) {
  107. [self reset];
  108. }
  109. return success;
  110. }
  111. - (void)setupVideoProgram{
  112. //Load vertex and fragment shaders
  113. GLint attribLocation[NUM_ATTRIBUTES] = {
  114. ATTRIB_VERTEX, ATTRIB_TEXTUREPOSITON,
  115. };
  116. GLchar *attribName[NUM_ATTRIBUTES] = {
  117. "position", "texturecoordinate",
  118. };
  119. const GLchar *vertSrc = [GLESUtils readFile:@"VideoVert.glsl"];
  120. const GLchar *fragSrc = [GLESUtils readFile:@"VideoFrag.glsl"];
  121. // shader program
  122. glueCreateProgram(vertSrc, fragSrc,
  123. NUM_ATTRIBUTES, (const GLchar **)&attribName[0], attribLocation,
  124. 0, 0, 0,
  125. &_program );
  126. }
  127. - (void)reset
  128. {
  129. EAGLContext *oldContext = [EAGLContext currentContext];
  130. if ( oldContext != _oglContext ) {
  131. if ( ! [EAGLContext setCurrentContext:_oglContext] ) {
  132. @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Problem with OpenGL context" userInfo:nil];
  133. return;
  134. }
  135. }
  136. if ( _frameBufferHandle ) {
  137. glDeleteFramebuffers( 1, &_frameBufferHandle );
  138. _frameBufferHandle = 0;
  139. }
  140. if ( _colorBufferHandle ) {
  141. glDeleteRenderbuffers( 1, &_colorBufferHandle );
  142. _colorBufferHandle = 0;
  143. }
  144. if ( _program ) {
  145. glDeleteProgram( _program );
  146. _program = 0;
  147. }
  148. if ( _textureCache ) {
  149. CFRelease( _textureCache );
  150. _textureCache = 0;
  151. }
  152. if ( oldContext != _oglContext ) {
  153. [EAGLContext setCurrentContext:oldContext];
  154. }
  155. }
  156. - (void)displayPixelBuffer:(CVPixelBufferRef )pixelBuffer
  157. {
  158. @synchronized (self) {
  159. if ( pixelBuffer == NULL ) {
  160. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL pixel buffer" userInfo:nil];
  161. return;
  162. }
  163. EAGLContext *oldContext = [EAGLContext currentContext];
  164. if ( oldContext != _oglContext ) {
  165. if ( ! [EAGLContext setCurrentContext:_oglContext] ) {
  166. @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Problem with OpenGL context" userInfo:nil];
  167. return;
  168. }
  169. }
  170. if ( _frameBufferHandle == 0 ) {
  171. BOOL success = [self initializeBuffers];
  172. if ( ! success ) {
  173. CVPixelBufferRelease(pixelBuffer);
  174. NSLog( @"Problem initializing OpenGL buffers." );
  175. return;
  176. }
  177. }
  178. // Create a CVOpenGLESTexture from a CVPixelBufferRef
  179. size_t frameWidth = CVPixelBufferGetWidth( pixelBuffer );
  180. size_t frameHeight = CVPixelBufferGetHeight( pixelBuffer );
  181. CVOpenGLESTextureRef texture = NULL;
  182. CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage( kCFAllocatorDefault,
  183. _textureCache,
  184. pixelBuffer,
  185. NULL,
  186. GL_TEXTURE_2D,
  187. GL_RGBA,
  188. (GLsizei)frameWidth,
  189. (GLsizei)frameHeight,
  190. GL_BGRA,
  191. GL_UNSIGNED_BYTE,
  192. 0,
  193. &texture );
  194. if ( ! texture || err ) {
  195. CVPixelBufferRelease(pixelBuffer);
  196. NSLog( @"CVOpenGLESTextureCacheCreateTextureFromImage failed (error: %d)", err );
  197. return;
  198. }
  199. // Set the view port to the entire view
  200. glBindFramebuffer( GL_FRAMEBUFFER, _frameBufferHandle );
  201. glViewport( 0, 0, _width, _height );
  202. glUseProgram( _program );
  203. glActiveTexture( GL_TEXTURE0 );
  204. glBindTexture( CVOpenGLESTextureGetTarget( texture ), CVOpenGLESTextureGetName( texture ) );
  205. glUniform1i( _frame, 0 );
  206. // Set texture parameters
  207. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  208. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  209. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
  210. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
  211. glVertexAttribPointer( ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices );
  212. glEnableVertexAttribArray( ATTRIB_VERTEX );
  213. // Preserve aspect ratio; fill layer bounds
  214. CGSize textureSamplingSize;
  215. CGSize cropScaleAmount = CGSizeMake( self.bounds.size.width / (float)frameWidth, self.bounds.size.height / (float)frameHeight );
  216. if ( cropScaleAmount.height > cropScaleAmount.width ) {
  217. textureSamplingSize.width = self.bounds.size.width / ( frameWidth * cropScaleAmount.height );
  218. textureSamplingSize.height = 1.0;
  219. }
  220. else {
  221. textureSamplingSize.width = 1.0;
  222. textureSamplingSize.height = self.bounds.size.height / ( frameHeight * cropScaleAmount.width );
  223. }
  224. // Perform a vertical flip by swapping the top left and the bottom left coordinate.
  225. // CVPixelBuffers have a top left origin and OpenGL has a bottom left origin.
  226. GLfloat passThroughTextureVertices[] = {
  227. ( 1.0 - textureSamplingSize.width ) / 2.0, ( 1.0 + textureSamplingSize.height ) / 2.0, // top left
  228. ( 1.0 + textureSamplingSize.width ) / 2.0, ( 1.0 + textureSamplingSize.height ) / 2.0, // top right
  229. ( 1.0 - textureSamplingSize.width ) / 2.0, ( 1.0 - textureSamplingSize.height ) / 2.0, // bottom left
  230. ( 1.0 + textureSamplingSize.width ) / 2.0, ( 1.0 - textureSamplingSize.height ) / 2.0, // bottom right
  231. };
  232. glVertexAttribPointer( ATTRIB_TEXTUREPOSITON, 2, GL_FLOAT, 0, 0, passThroughTextureVertices );
  233. glEnableVertexAttribArray( ATTRIB_TEXTUREPOSITON );
  234. glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
  235. glBindRenderbuffer( GL_RENDERBUFFER, _colorBufferHandle );
  236. [_oglContext presentRenderbuffer:GL_RENDERBUFFER];
  237. glBindTexture( CVOpenGLESTextureGetTarget( texture ), 0 );
  238. glBindTexture( GL_TEXTURE_2D, 0 );
  239. CFRelease( texture );
  240. if (oldContext != _oglContext) {
  241. [EAGLContext setCurrentContext:oldContext];
  242. }
  243. }
  244. }
  245. - (void)flushPixelBufferCache
  246. {
  247. if ( _textureCache ) {
  248. CVOpenGLESTextureCacheFlush(_textureCache, 0);
  249. }
  250. }
  251. @end