#import #import "GPUImageLayer.h" #import "GPUImageOutput.h" #import "GPUImageFilter.h" @interface GPUImageLayer() { GPUImageFramebuffer *inputFramebufferForDisplay; GLProgram *displayProgram; GLint displayPositionAttribute, displayTextureCoordinateAttribute; GLint displayInputTextureUniform; CGSize inputImageSize; GLfloat imageVertices[8]; GLfloat backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha; } // Initialization and teardown - (void)commonInit; // Managing the display FBOs - (void)createDisplayFramebuffer; - (void)destroyDisplayFramebuffer; // Handling fill mode - (void)recalculateViewGeometry; @end @implementation GPUImageLayer @synthesize sizeInPixels = _sizeInPixels; @synthesize fillMode = _fillMode; @synthesize enabled; #pragma mark - #pragma mark Initialization and teardown - (instancetype)init { self = [super init]; if (self) { [self commonInit]; } return self; } - (void)commonInit; { // I believe each of these views needs a separate OpenGL context, unlike on iOS where you're rendering to an FBO in a layer // NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = { // NSOpenGLPFADoubleBuffer, // NSOpenGLPFAAccelerated, 0, // 0 // }; // // NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes]; // if (pixelFormat == nil) // { // NSLog(@"Error: No appropriate pixel format found"); // } // // TODO: Take into account the sharegroup // NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:[[GPUImageContext sharedImageProcessingContext] context]]; // if (context == nil) // { // NSAssert(NO, @"Problem creating the GPUImageView context"); // } // [self setOpenGLContext:context]; // [self setOpenGLContext:[[GPUImageContext sharedImageProcessingContext] context]]; CGLSetCurrentContext([GPUImageContext sharedImageProcessingContext].context.CGLContextObj); self.asynchronous = NO; inputRotation = kGPUImageNoRotation; // self.hidden = NO; self.enabled = YES; runSynchronouslyOnVideoProcessingQueue(^{ CGLSetCurrentContext([GPUImageContext sharedImageProcessingContext].context.CGLContextObj); // [self.openGLContext makeCurrentContext]; displayProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImagePassthroughFragmentShaderString]; // displayProgram = [[GLProgram alloc] initWithVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImagePassthroughFragmentShaderString]; if (!displayProgram.initialized) { [displayProgram addAttribute:@"position"]; [displayProgram addAttribute:@"inputTextureCoordinate"]; if (![displayProgram link]) { NSString *progLog = [displayProgram programLog]; NSLog(@"Program link log: %@", progLog); NSString *fragLog = [displayProgram fragmentShaderLog]; NSLog(@"Fragment shader compile log: %@", fragLog); NSString *vertLog = [displayProgram vertexShaderLog]; NSLog(@"Vertex shader compile log: %@", vertLog); displayProgram = nil; NSAssert(NO, @"Filter shader link failed"); } } displayPositionAttribute = [displayProgram attributeIndex:@"position"]; displayTextureCoordinateAttribute = [displayProgram attributeIndex:@"inputTextureCoordinate"]; displayInputTextureUniform = [displayProgram uniformIndex:@"inputImageTexture"]; [GPUImageContext setActiveShaderProgram:displayProgram]; // [displayProgram use]; glEnableVertexAttribArray(displayPositionAttribute); glEnableVertexAttribArray(displayTextureCoordinateAttribute); [self setBackgroundColorRed:0.0 green:0.0 blue:0.0 alpha:1.0]; _fillItMode = kGPUImageFillModePreserveAspectRatio; [self createDisplayFramebuffer]; }); } - (void)dealloc { } #pragma mark - #pragma mark Managing the display FBOs - (void)createDisplayFramebuffer; { // Perhaps I'll use an FBO at some time later, but for now will render directly to the screen _sizeInPixels.width = self.bounds.size.width; _sizeInPixels.height = self.bounds.size.height; // NSLog(@"Backing width: %d, height: %d", backingWidth, backingHeight); } - (void)destroyDisplayFramebuffer; { CGLSetCurrentContext([GPUImageContext sharedImageProcessingContext].context.CGLContextObj); // [self.openGLContext makeCurrentContext]; } - (void)setDisplayFramebuffer; { glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindRenderbuffer(GL_RENDERBUFFER, 0); glViewport(0, 0, (GLint)_sizeInPixels.width, (GLint)_sizeInPixels.height); } - (void)presentFramebuffer; { CGLSetCurrentContext([GPUImageContext sharedImageProcessingContext].context.CGLContextObj); // [self.openGLContext flushBuffer]; CGLFlushDrawable([GPUImageContext sharedImageProcessingContext].context.CGLContextObj); } - (void)setFrame:(CGRect)frame { [super setFrame:frame]; if ( (_sizeInPixels.width == self.bounds.size.width) && (_sizeInPixels.height == self.bounds.size.height) ) { return; } _sizeInPixels.width = self.bounds.size.width; _sizeInPixels.height = self.bounds.size.height; [self recalculateViewGeometry]; /* dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self newFrameReadyAtTime:kCMTimeInvalid atIndex:0]; }); */ [self setNeedsDisplay]; } #pragma mark - #pragma mark Handling fill mode - (void)recalculateViewGeometry; { // runSynchronouslyOnVideoProcessingQueue(^{ CGFloat heightScaling, widthScaling; CGSize currentViewSize = self.bounds.size; // CGFloat imageAspectRatio = inputImageSize.width / inputImageSize.height; // CGFloat viewAspectRatio = currentViewSize.width / currentViewSize.height; CGRect insetRect = AVMakeRectWithAspectRatioInsideRect(inputImageSize, self.bounds); switch(_fillItMode) { case kGPUImageFillModeStretch: { widthScaling = 1.0; heightScaling = 1.0; }; break; case kGPUImageFillModePreserveAspectRatio: { widthScaling = insetRect.size.width / currentViewSize.width; heightScaling = insetRect.size.height / currentViewSize.height; }; break; case kGPUImageFillModePreserveAspectRatioAndFill: { // CGFloat widthHolder = insetRect.size.width / currentViewSize.width; widthScaling = currentViewSize.height / insetRect.size.height; heightScaling = currentViewSize.width / insetRect.size.width; }; break; } imageVertices[0] = -widthScaling; imageVertices[1] = -heightScaling; imageVertices[2] = widthScaling; imageVertices[3] = -heightScaling; imageVertices[4] = -widthScaling; imageVertices[5] = heightScaling; imageVertices[6] = widthScaling; imageVertices[7] = heightScaling; // }); // static const GLfloat imageVertices[] = { // -1.0f, -1.0f, // 1.0f, -1.0f, // -1.0f, 1.0f, // 1.0f, 1.0f, // }; } - (void)setBackgroundColorRed:(GLfloat)redComponent green:(GLfloat)greenComponent blue:(GLfloat)blueComponent alpha:(GLfloat)alphaComponent; { backgroundColorRed = redComponent; backgroundColorGreen = greenComponent; backgroundColorBlue = blueComponent; backgroundColorAlpha = alphaComponent; } + (const GLfloat *)textureCoordinatesForRotation:(GPUImageRotationMode)rotationMode; { // static const GLfloat noRotationTextureCoordinates[] = { // 0.0f, 0.0f, // 1.0f, 0.0f, // 0.0f, 1.0f, // 1.0f, 1.0f, // }; static const GLfloat noRotationTextureCoordinates[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; static const GLfloat rotateRightTextureCoordinates[] = { 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }; static const GLfloat rotateLeftTextureCoordinates[] = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; static const GLfloat verticalFlipTextureCoordinates[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, }; static const GLfloat horizontalFlipTextureCoordinates[] = { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, }; static const GLfloat rotateRightVerticalFlipTextureCoordinates[] = { 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static const GLfloat rotateRightHorizontalFlipTextureCoordinates[] = { 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }; static const GLfloat rotate180TextureCoordinates[] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, }; switch(rotationMode) { case kGPUImageNoRotation: return noRotationTextureCoordinates; case kGPUImageRotateLeft: return rotateLeftTextureCoordinates; case kGPUImageRotateRight: return rotateRightTextureCoordinates; case kGPUImageFlipVertical: return verticalFlipTextureCoordinates; case kGPUImageFlipHorizonal: return horizontalFlipTextureCoordinates; case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates; case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates; case kGPUImageRotate180: return rotate180TextureCoordinates; } } #pragma mark - #pragma mark GPUInput protocol - (void)drawInCGLContext:(CGLContextObj)ctx pixelFormat:(CGLPixelFormatObj)pf forLayerTime:(CFTimeInterval)t displayTime:(const CVTimeStamp *)ts { CGLSetCurrentContext(ctx); NSLog(@"Draw in CGLContext"); runSynchronouslyOnVideoProcessingQueue(^{ // [[self openGLContext] makeCurrentContext]; [GPUImageContext setActiveShaderProgram:displayProgram]; [self setDisplayFramebuffer]; [displayProgram use]; // glMatrixMode(GL_MODELVIEW); // glLoadIdentity(); // // glMatrixMode(GL_PROJECTION); // glLoadIdentity(); glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_2D, [inputFramebufferForDisplay texture]); glUniform1i(displayInputTextureUniform, 4); glVertexAttribPointer(displayPositionAttribute, 2, GL_FLOAT, 0, 0, imageVertices); glVertexAttribPointer(displayTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [GPUImageLayer textureCoordinatesForRotation:inputRotation]); BOOL canLockFocus = YES; /* if ([self respondsToSelector:@selector(lockFocusIfCanDraw)]) { canLockFocus = [self lockFocusIfCanDraw]; } else { [self lockFocus]; } *//* */ if (canLockFocus) { glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); [self presentFramebuffer]; glBindTexture(GL_TEXTURE_2D, 0); // [self unlockFocus]; } [inputFramebufferForDisplay unlock]; inputFramebufferForDisplay = nil; }); [super drawInCGLContext:ctx pixelFormat:pf forLayerTime:t displayTime:ts]; } - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex; { NSLog(@"New frame ready at time %i"); [self setNeedsDisplay]; } - (NSInteger)nextAvailableTextureIndex; { return 0; } - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex; { inputFramebufferForDisplay = newInputFramebuffer; [inputFramebufferForDisplay lock]; } - (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex; { inputRotation = newInputRotation; } - (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex; { runSynchronouslyOnVideoProcessingQueue(^{ CGSize rotatedSize = newSize; if (GPUImageRotationSwapsWidthAndHeight(inputRotation)) { rotatedSize.width = newSize.height; rotatedSize.height = newSize.width; } if (!CGSizeEqualToSize(inputImageSize, rotatedSize)) { inputImageSize = rotatedSize; [self recalculateViewGeometry]; } }); } - (CGSize)maximumOutputSize; { if ([self respondsToSelector:@selector(setContentScaleFactor:)]) { CGSize pointSize = self.bounds.size; // TODO: Account for Retina displays return pointSize; // return CGSizeMake(self.contentScaleFactor * pointSize.width, self.contentScaleFactor * pointSize.height); } else { return self.bounds.size; } } - (void)endProcessing { } - (BOOL)shouldIgnoreUpdatesToThisTarget; { return NO; } - (void)conserveMemoryForNextFrame; { } - (BOOL)wantsMonochromeInput; { return NO; } - (void)setCurrentlyReceivingMonochromeInput:(BOOL)newValue; { } #pragma mark - #pragma mark Accessors - (CGSize)sizeInPixels; { if (CGSizeEqualToSize(_sizeInPixels, CGSizeZero)) { return [self maximumOutputSize]; } else { return _sizeInPixels; } } - (void)setFillMode:(GPUImageFillModeType)newValue; { _fillItMode = newValue; [self recalculateViewGeometry]; } @end