#import "GPUImageTwoInputFilter.h" NSString *const kGPUImageTwoInputTextureVertexShaderString = SHADER_STRING ( attribute vec4 position; attribute vec4 inputTexCoord; attribute vec4 inputTexCoord2; varying vec2 texCoord; varying vec2 texCoord2; void main() { gl_Position = position; texCoord = inputTexCoord.xy; texCoord2 = inputTexCoord2.xy; } ); @implementation GPUImageTwoInputFilter #pragma mark - #pragma mark Initialization and teardown - (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString { if (!(self = [self initWithVertexShaderFromString:kGPUImageTwoInputTextureVertexShaderString fragmentShaderFromString:fragmentShaderString])) { return nil; } return self; } - (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString { if (!(self = [super initWithVertexShaderFromString:vertexShaderString fragmentShaderFromString:fragmentShaderString])) { return nil; } inputRotation2 = kGPUImageNoRotation; hasSetFirstTexture = NO; hasReceivedFirstFrame = NO; hasReceivedSecondFrame = NO; firstFrameWasVideo = NO; secondFrameWasVideo = NO; firstFrameCheckDisabled = NO; secondFrameCheckDisabled = NO; firstFrameTime = kCMTimeInvalid; secondFrameTime = kCMTimeInvalid; runSynchronouslyOnVideoProcessingQueue(^{ [GPUImageContext useImageProcessingContext]; filterSecondTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTexCoord2"]; filterInputTextureUniform2 = [filterProgram uniformIndex:@"inputImageTexture2"]; glEnableVertexAttribArray(filterSecondTextureCoordinateAttribute); }); return self; } - (void)initializeAttributes { [super initializeAttributes]; [filterProgram addAttribute:@"inputTexCoord2"]; } - (void)disableFirstFrameCheck { firstFrameCheckDisabled = YES; } - (void)disableSecondFrameCheck { secondFrameCheckDisabled = YES; } #pragma mark - #pragma mark Rendering - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates { if (self.preventRendering) { [firstInputFramebuffer unlock]; [secondInputFramebuffer unlock]; return; } [GPUImageContext setActiveShaderProgram:filterProgram]; outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO]; [outputFramebuffer activateFramebuffer]; if (usingNextFrameForImageCapture) { [outputFramebuffer lock]; } [self setUniformsForProgramAtIndex:0]; glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]); glUniform1i(filterInputTextureUniform, 2); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, [secondInputFramebuffer texture]); glUniform1i(filterInputTextureUniform2, 3); glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices); glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates); glVertexAttribPointer(filterSecondTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [[self class] textureCoordinatesForRotation:inputRotation2]); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); [firstInputFramebuffer unlock]; [secondInputFramebuffer unlock]; if (usingNextFrameForImageCapture) { dispatch_semaphore_signal(imageCaptureSemaphore); } } #pragma mark - #pragma mark GPUImageInput - (NSInteger)nextAvailableTextureIndex { if (hasSetFirstTexture) { return 1; } else { return 0; } } - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex { if (textureIndex == 0) { firstInputFramebuffer = newInputFramebuffer; hasSetFirstTexture = YES; [firstInputFramebuffer lock]; } else { secondInputFramebuffer = newInputFramebuffer; [secondInputFramebuffer lock]; } } - (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex { if (textureIndex == 0) { [super setInputSize:newSize atIndex:textureIndex]; if (CGSizeEqualToSize(newSize, CGSizeZero)) { hasSetFirstTexture = NO; } } } - (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex { if (textureIndex == 0) { inputRotation = newInputRotation; } else { inputRotation2 = newInputRotation; } } - (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)textureIndex { CGSize rotatedSize = sizeToRotate; GPUImageRotationMode rotationToCheck; if (textureIndex == 0) { rotationToCheck = inputRotation; } else { rotationToCheck = inputRotation2; } if (GPUImageRotationSwapsWidthAndHeight(rotationToCheck)) { rotatedSize.width = sizeToRotate.height; rotatedSize.height = sizeToRotate.width; } return rotatedSize; } - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex { // You can set up infinite update loops, so this helps to short circuit them if (hasReceivedFirstFrame && hasReceivedSecondFrame) { return; } BOOL updatedMovieFrameOppositeStillImage = NO; if (textureIndex == 0) { hasReceivedFirstFrame = YES; firstFrameTime = frameTime; if (secondFrameCheckDisabled) { hasReceivedSecondFrame = YES; } if (!CMTIME_IS_INDEFINITE(frameTime)) { if CMTIME_IS_INDEFINITE(secondFrameTime) { updatedMovieFrameOppositeStillImage = YES; } } } else { hasReceivedSecondFrame = YES; secondFrameTime = frameTime; if (firstFrameCheckDisabled) { hasReceivedFirstFrame = YES; } if (!CMTIME_IS_INDEFINITE(frameTime)) { if CMTIME_IS_INDEFINITE(firstFrameTime) { updatedMovieFrameOppositeStillImage = YES; } } } // || (hasReceivedFirstFrame && secondFrameCheckDisabled) || (hasReceivedSecondFrame && firstFrameCheckDisabled) if ((hasReceivedFirstFrame && hasReceivedSecondFrame)) { CMTime passOnFrameTime = (!CMTIME_IS_INDEFINITE(firstFrameTime)) ? firstFrameTime : secondFrameTime; [super newFrameReadyAtTime:passOnFrameTime atIndex:0]; // Bugfix when trying to record: always use time from first input (unless indefinite, in which case use the second input) hasReceivedFirstFrame = NO; hasReceivedSecondFrame = NO; } } @end