当前位置: 首页 > 工具软件 > GPUImage 3 > 使用案例 >

GPUImage 源码解析

尹凌龙
2023-12-01

GPUImage 实现链式编程的原理

  1. 链式编程的开始,所有的数据源都继承自 GPUImageOutput
  2. 链式编程的过程,所有的 filter 都继承自 GPUImageFilter , GPUImageFilter 继承自 GPUImageOutput 并且GPUImageFilter 遵守 GPUImageInput 协议; 所以所有的filter都可以作为输入源和输出;
  3. GPUImageView 继承自UIView, 并且遵守 GPUImageInput 协议, 可以作为输入, 不能输出, 作为这个链式编程的终点;
    过程 :
GPUImageOutput -> (GPUImageFilter : GPUImageOutput <GPUImageInput> ) -> (GPUImageFilter : GPUImageOutput <GPUImageInput> ) -> (GPUImageFilter : GPUImageOutput <GPUImageInput> ) -> (GPUImageView <GPUImageInput>)

实现过程

  1. [[GPUImagePicture alloc] initWithImage:inputImage] 时候会生成一个 outputFramebuffer;

  2. [sourcePicture addTarget:filter1]; -> [sourcePicture setInputFramebufferForTarget:newTarget atIndex:textureLocation]; -> [filter1 setInputFramebuffer:outputFramebuffer atIndex:inputTextureIndex]; 即会把 filter1 添加到 sourcePicture.targets 数组中, 并且设置 filter1.firstInputFramebuffer = sourcePicture.outputFramebuffer

  3. [sourcePicture processImage] 函数的流程

    1. [filter1 setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget]; 设置 filter1.firstInputFramebuffer = sourcePicture.outputFramebuffer;
    2. [filter1 newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureIndexOfTarget];
      1. [filter1 renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]]; 这个函数会先生成一个 outputFramebuffer, 然后将 将firstInputFramebuffer.texture 绘制到 outputFramebuffer的帧缓冲区中;
      2. [filter1 informTargetsAboutNewFrameAtTime:frameTime]; 会遍历 filter1的所有 targets, 之后将每个target作为 filter1, 将 3.2.1 中生成的 outputFramebuffer 作为 outputFramebuffer, 一直调用 3.1 和 3.2; 直到 target 不再是 GPUImageOutput, 即 taget 为 GPUImageView时候, 设置 inputFramebufferForDisplay = outputFramebuffer;
  4. 当遍历到的 target 为 GPUImageView 时候, 调用 GPUImageView的newFrameReadyAtTime:atIndex 方法, 在这个方法中, 直接将 inputFramebufferForDisplay的纹理纹绘制到当前的帧缓冲区中,并且将当前的帧缓冲显示到屏幕上;

    UIImage *inputImage = [UIImage imageNamed:@"gyy"];
    GPUImagePicture *sourcePicture = [[GPUImagePicture alloc] initWithImage:inputImage];
    
    GPUImageBrightnessFilter *filter1 = [[GPUImageBrightnessFilter alloc] init];
    GPUImageSepiaFilter *filter2 = [[GPUImageSepiaFilter alloc] init];
    
    [sourcePicture addTarget:filter1];
    [filter1 addTarget:filter2];
    [filter2 addTarget:self.imageView];
    
    [sourcePicture processImage];
    
    self.valueChanged = ^(UISlider *slider) {
        CGFloat midpoint = [(UISlider *)slider value];
        filter2.intensity = midpoint;
        filter1.brightness = midpoint;
        [sourcePicture processImage];
    };
    

关键的类的关键代码的分析

  1. GLProgram
    GLProgram 是对于 program 的封装就, 这里边包括了 program 的编译, 链接和属性赋值, 注意这里有个initialized 属性, 只有当 programe link 过了,这个 initialized 的值才会为 YES;

  2. GPUImageContext
    对于 context 和 context 的各种操作的封装, 包括了 context, shaderProgramCache(字典, 是GLProgram 的缓存, 每次去 program 时, 先去shaderProgramCache中取 ,取到就使用, 没有取到就创建.) framebufferCache(GPUImageFramebufferCache 是对GPUImageFramebuffer 的缓存封装)

  3. GPUImageFramebufferCache
    对于 GPUImageFramebuffer 的缓存的封装, 用 texture 的属性和framebuffer 的 size作为 key, GPUImageFramebuffer 作为 value;

  4. GPUImageFramebuffer
    对于 framebuffer 的封装. 初始化时候需要传入一个参数 onlyGenerateTexture, 如果 onlyGenerateTexture = YES, 则只是生成一个 framebuffer, 并设置texture 的一些通用属性, 如果 onlyGenerateTexture = NO, 那么, 就会生成 framebuffer 的同时, 会生成一个 texture 纹理, 并且纹理会绑定到生成的 framebuffer 中; 还可以将当前的帧缓冲中的纹理转化为 UIImage; renderTarget (当前帧缓冲区的数据, 转换成 CVPixelBufferRef 的数据)

    // - 创建一个 CVPixelBuffer,  将最终的像素数据写到 renderTarget 中  由CVPixelBufferCreate()函数生成了一个CVPixelBufferRef对象,该对象作为像素数据的载体,最终由CVOpenGLESTextureCacheCreateTextureFromImage()函数传递给opengl es
    CVReturn err = CVPixelBufferCreate(kCFAllocatorDefault, (int)_size.width, (int)_size.height, kCVPixelFormatType_32BGRA, attrs, &renderTarget);
    
    // -  根据  renderTarget 中的像素数据, 生成一个纹理 id
    /* renderTarget像素数据传给opengl es,类似于相当于glTexImage2D(),当	然renderTarget中数据可以是由CVPixelBufferCreate()创建的默认值都是0的像素数据,也可以是具体的像素数据*/
    err = CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,
                                                                NULL, // texture attributes
                                                                GL_TEXTURE_2D,
                                                                _textureOptions.internalFormat, // opengl format
                                                                (int)_size.width,
                                                                (int)_size.height,
                                                                _textureOptions.format, // native iOS format
                                                                _textureOptions.type,
                                                                0,
                                                                &renderTexture);
    
    // -  将纹理附加到帧缓存上, 以后获取当前的帧缓冲的纹理, 实际上就是获取使用着色器程序后最终存储在帧缓冲的结果.在 GPUImage 中; glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 会根据 shader 绘制数据到 当前的outputFramebufferCurrent上;  每个 filterCurrent 都是用上一个 filterLast 的 outputFramebufferLast 的 纹理, 作为输入纹理, 然后根据当前的shader 将outputFramebufferLast的纹理绘制到当前的帧缓冲 outputFramebufferCurrent上的;      
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
    
    /** 创建一个帧缓冲的纹理 (和创建普通的纹理对象一样, 只不过最后一个参数不是图片数据, 而是 NULL) */
    glTexImage2D(GL_TEXTURE_2D, 0, _textureOptions.internalFormat, (int)_size.width, (int)_size.height, 0, _textureOptions.format, _textureOptions.type, NULL);
    
    
  5. GPUImageInput
    是一个协议, 遵守这个协议的类代表可以作为源数据的输入, 输入的是 GPUImageFramebuffer

    // - 准备下一个要使用的帧
    - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
    // 设置输入的帧缓冲对象以及纹理索引
    - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
    
  6. GPUImageOutput
    表示该类可以作为源数据的输出, 输入出的 GPUImageFramebuffer
    关键代码调用流程

    // - 从当前的帧缓冲中获取图片
    - (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;
    
    // - 添加 target
    - (void)addTarget:(id<GPUImageInput>)newTarget; 
    	--> - (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation; 
    		-->[self setInputFramebufferForTarget:newTarget atIndex:textureLocation];
    			--> [target setInputFramebuffer:[self framebufferForOutput] atIndex:inputTextureIndex];
    
  7. GPUImageFilter
    继承自 GPUImageOutput 并且遵守 GPUImageInput 协议; 所以 GPUImageFilter 可以作为输入, 也可以作为输出, 这就是链式编程实现的关键

    // -  赋值firstInputFramebuffer
    - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
    
    // - 准备下一个要使用的帧
    - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
    	// - 生成 outputFramebuffer, 并将 firstInputFramebuffer 的纹理作为输入,结合 shader 绘制到 outputFramebuffer 的帧缓冲中;
    	-->  - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates
    	// - 遍历 self.targets, 设置 target.firstInputFramebuffer = outputFramebuffer;然后 调用 target 的 newFrameReadyAtTime:atIndex:方法;
    	--> - (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime;
    
  8. GPUImagePicture
    继承自 GPUImageOutput, 封装了静态图片; 将图片转为纹理并且绑定到outputFramebuffer.texture

    // - 生成 outputFramebuffer, 将图片数据转为纹理, 绘制到outputFramebuffer的帧缓冲区中.
    - (id)initWithCGImage:(CGImageRef)newImageSource smoothlyScaleOutput:(BOOL)smoothlyScaleOutput removePremultiplication:(BOOL)removePremultiplication;
    // - 同 GPUImageOutput 的方法调用
    - (void)addTarget:(id<GPUImageInput>)newTarget;
    // - 处理数据
    - (void)processImage;
    	--> - (BOOL)processImageWithCompletionHandler:(void (^)(void))completion;
    		// - 同  GPUImageFilter 的 setInputFramebuffer:atIndex: 和 newFrameReadyAtTime:的方法一样
    		--> [currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
    		--> [currentTarget newFrameReadyAtTime:currentTime atIndex:textureIndexOfTarget];
    
  9. GPUImageUIElement
    继承自 GPUImageOutput, 将 view 转为 image 数据;

    // - 将 view 转为 image 数据; 生成 outputFramebuffer中, 将图片转为纹理并且绑定到 outputFramebuffer.texture
    - (void)update;
    	// - 调用 target 的 newFrameReadyAtTime:fatIndex:的方法
    	--> [currentTarget newFrameReadyAtTime:frameTime atIndex:textureIndexOfTarget];
    
    
  10. GPUImageRawDataInput 和 GPUImageTextureInput 内部实现和 GPUImagePicture 相似

  11. GPUImageVideoCamera
    继承自 GPUImageOutput, 封装了相机的初始化, 视频的捕捉, 每次捕捉到一帧画面, 就将画面绘制到当前的这缓冲区中, 并且将 outputFramebuffer 传递给下一个 target 作为 target 的 firstInputFramebuffer;

    // - 捕捉到每帧的数据的回调
    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
    	// - 处理每一帧的视频数据
    	-->  - (void)processVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer;
    		// - 生成 outputFramebuffer, 并将 帧数据绘制到 outputFramebuffer的 帧缓冲区中
    		--> - (void)convertYUVToRGBOutput
    		// - 通知 self.targets 中的 target, 并设置 target.firstInputFramebuffer = self.outputFramebuffer;
    		--> - (void)updateTargetsForVideoCameraUsingCacheTextureAtWidth:(int)bufferWidth height:(int)bufferHeight time:(CMTime)currentTime;
    			--> [currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
    			--> [currentTarget newFrameReadyAtTime:currentTime atIndex:textureIndexOfTarget];
    
  12. GPUImageStillCamera
    继承自 GPUImageVideoCamera, 封装了拍照的功能;

    // - 将 摄像头捕捉到的数据转为 CVImageBufferRef, 之后将 CVImageBufferRef, 传递给父类的 - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection; 方法, 之后调用 GPUImageOutput 的 - (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;方法, 返回一个图片
    - (void)capturePhotoProcessedUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withImageOnGPUHandler:(void (^)(NSError *error))block
    
  13. GPUImageMovie
    继承自 GPUImageOutput, 用于给视频添加滤镜, 可以传入 AVAsset 或者 AVPlayerItem; 具体处理视频的实现和 GPUImageVideoCamera 基本相同

  14. GPUImageFilterGroup
    继承自 GPUImageOutput, 并且遵守 GPUImageInput 协议

    // - 记录所有的 filter
    - (void)addFilter:(GPUImageOutput<GPUImageInput> *)newFilter;
    // - 给_terminalFilter 设置 _terminalFilter.firstInputFramebuffer = newTarget.outputFramebuffer; 这里需要每次加入一个新的 target 时候, 设置 _terminalFilter = newFilter;
    - (void)addTarget:(id<GPUImageInput>)newTarget;
    	--> [_terminalFilter addTarget:newTarget atTextureLocation:textureLocation];
    // - 设置 initialFilters[0].firstInputFramebuffer = newTarget.outputFramebuffer; [picture addTarget:self.myFilterGroup]; 时候内部会调用这个函数;
    - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
    	--> [initialFilters[0] setInputFramebuffer:newInputFramebuffer atIndex:textureIndex];
    // - 调用 initialFilters[0] 的 newFrameReadyAtTime:frameTimeatIndex:函数 来实现链式编程 调用 [picture processImage]; 时候会调用这个函数;
    - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
    	--> [initialFilters[0] newFrameReadyAtTime:frameTime atIndex:textureIndex];
    
  15. GPUImageMovieWriter
    遵守 GPUImageInput 协议, 主要用来将捕捉到的视频帧数据保存到本地;

    // - firstInputFramebuffer = newInputFramebuffer [filter addTarget:movieWriter]; 时候调用
    - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
    
    // - 采集到的每一帧的数据, 都会调用这个方法
    - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
    	// - 调用 createDataFBO, 将 inputFramebufferToUse 的纹理绘制到当前的当前的帧缓冲中;
    	--> - (void)renderAtInternalSizeUsingFramebuffer:(GPUImageFramebuffer *)inputFramebufferToUse;
    		// - 生成 framebuffer, 并且将生成的纹理绑定到帧缓冲中; 同时将当前帧缓冲中的数据,生成CVPixelBufferRef;
    		--> - (void)createDataFBO;
    	--> 调用 write()这个 block, 将 CVPixelBufferRef 写入到本地;
    
  16. GPUImageView
    遵守 GPUImageInput 协议 用来将绘制的结果显示到屏幕上.

    // - inputFramebufferForDisplay = newInputFramebuffer; 保存传入的 framebuffer
    - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
    
    // - 将 inputFramebufferForDisplay 的纹理绘制到当前的纹理缓冲区,并且将绘制的结果显示到屏幕上
    - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
    
  17. GPUImageRawDataOutput 和 GPUImageTextureOutput 将输入的帧缓冲转为原始数据或者纹理;

  18. GPUImageTwoInputFilter GPUImageThreeInputFilter GPUImageFourInputFilter 基本相似, 就是在 GPUImageFilter 的基础上 增加了 secondInputFramebuffer, thirdInputFramebuffer, fourthInputFramebuffer, 先是在 setInputFramebuffer:atIndex: 中记录 secondInputFramebuffer, thirdInputFramebuffer, fourthInputFramebuffer, , 然后在 renderToTextureWithVertices:textureCoordinates: 函数中 激活这些 inputFramebuffer 的 texture; 然后将结果绘制到 outputFramebuffer 的帧缓冲区中

    // - 关键函数
    - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
    - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
    
  19. GPUImageTwoPassFilter
    GPUImageTwoPassFilter 继承自 GPUImageFilter , 这个类多了个 secondOutputFramebuffer 和 secondFilterProgram , 它的作用是 将 firstInputFramebuffer 结合 filterProgram 绘制到 outputFramebuffer 的帧缓冲区中, 然后 结合 secondFilterProgram 和 outputFramebuffer 的纹理, 将最后结果绘制到 secondOutputFramebuffer 中;

    // - 关键函数
    - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
    
  20. GPUImageTwoPassTextureSamplingFilter

  21. GPUImage3x3TextureSamplingFilter

  22. GPUImageTwoInputCrossTextureSamplingFilter

  23. GPUImageBuffer
    (以上几个看了下源码 没看懂干啥用的, 先留着)
    项目地址(加注释)

 类似资料: