GPUImage源码解读(四)

GPUImageFramebuffer类用于管理帧缓冲对象,负责帧缓冲对象的创建和销毁,读取帧缓冲内容,其中纹理附件涉及到了相关的纹理选项。因此,它提供的属性也是和帧缓存、纹理附件、纹理选项等相关。

  • 属性

// 帧缓存大小
@property(readonly) CGSize size;
// 纹理选项
@property(readonly) GPUTextureOptions textureOptions;
// 纹理缓存
@property(readonly) GLuint texture;
// 是否仅有纹理没有帧缓存
@property(readonly) BOOL missingFramebuffer;
  • 初始化方法

// Initialization and teardown 初始化方法
- (id)initWithSize:(CGSize)framebufferSize;
- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;
- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;

其中 - (id)initWithSize:(CGSize)framebufferSize; 方法使用默认的纹理选项 GPUTextureOptions 然后调用[self initWithSize:framebufferSize textureOptions:defaultTextureOptions onlyTexture:NO] 方法. 纹理选项是给glTexParameteri 方法使用.

- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;
{
    if (!(self = [super init]))
    {
        return nil;
    }
     // 纹理选项
    _textureOptions = fboTextureOptions;
    _size = framebufferSize;
    framebufferReferenceCount = 0;
    referenceCountingDisabled = NO;
     // 是否只生成纹理缓存
    _missingFramebuffer = onlyGenerateTexture;
     // 如果只生成纹理缓存,则不生成帧缓存
    if (_missingFramebuffer)
    {
        runSynchronouslyOnVideoProcessingQueue(^{
            [GPUImageContext useImageProcessingContext];
            [self generateTexture];
            framebuffer = 0;
        });
    }
    else
    {
          // 既生成纹理缓存又生成帧缓存
        [self generateFramebuffer];
    }
    return self;
}

也可以使用自定义SIZE和纹理ID初始化 生成帧缓存对象

- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;
{
    if (!(self = [super init]))
    {
        return nil;
    }

    //纹理选项
    GPUTextureOptions defaultTextureOptions;
    defaultTextureOptions.minFilter = GL_LINEAR;
    defaultTextureOptions.magFilter = GL_LINEAR;
    defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
    defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
    defaultTextureOptions.internalFormat = GL_RGBA;
    defaultTextureOptions.format = GL_BGRA;
    defaultTextureOptions.type = GL_UNSIGNED_BYTE;

    _textureOptions = defaultTextureOptions;
    _size = framebufferSize;
    framebufferReferenceCount = 0;
    referenceCountingDisabled = YES;

    _texture = inputTexture;

    return self;
}

其它方法定义

// Usage 与使用当前帧缓存相关的方法,
- (void)activateFramebuffer;

// Reference counting 与 GPUImageFramebuffer 引用计数相关的方法,
- (void)lock;
- (void)unlock;
- (void)clearAllLocks;
- (void)disableReferenceCounting;
- (void)enableReferenceCounting;

// Image capture 从帧缓存生成位图相关的方法,
- (CGImageRef)newCGImageFromFramebufferContents;
- (void)restoreRenderTarget;

// Raw data bytes 获取帧缓存原始数据相关的方法。
- (void)lockForReading;
- (void)unlockAfterReading;
- (NSUInteger)bytesPerRow;
- (GLubyte *)byteBuffer;
  • # 激活。在使用帧缓存的时候首先要激活(即绑定为当前帧缓存),激活之后才能在当前帧缓存上进行相关操作。

- (void)activateFramebuffer;
{
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
      // 激活的时候需要设置视口大小
    glViewport(0, 0, (int)_size.width, (int)_size.height);
}
  • 计数器和析构方法 注意 当创建GPUImageFramebuffer时给onlyTexture参数填NO(一般就是填NO的)时,会创建一个CVPixelBufferRef类型的变量renderTarget,当用CFRelease去释放这个变量时,它占用的内存并不会立即释放,而是要调用CVOpenGLESTextureCacheFlush([[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], 0); 之后,才会真正释放内存。而在GPUImageFramebufferCache的purgeAllUnassignedFramebuffers方法中,会帮我们清空OpenGLESTextureCache,真正释放GPUImageFramebuffer占用内存。purgeAllUnassignedFramebuffers方法会在收到memory warning时触发释放内存,一般情况下无需自行调用。

  • 从帧缓存中生成图片。在读取图片数据的时候,根据设备是否支持 CoreVideo 框架,GPUImage 会选择使用 CVPixelBufferGetBaseAddress 或者 glReadPixels 读取帧缓存中的数据。最后通过 CGImageCreate,创建 CGImage 对象并返回该对象。

- (CGImageRef)newCGImageFromFramebufferContents;
{
    // a CGImage can only be created from a 'normal' color texture
    NSAssert(self.textureOptions.internalFormat == GL_RGBA, @"For conversion to a CGImage the output texture format for this filter must be GL_RGBA.");
    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.");

    __block CGImageRef cgImageFromBytes;

    // 在VideoProcessingQueue中进行同步处理
    runSynchronouslyOnVideoProcessingQueue(^{
        [GPUImageContext useImageProcessingContext];

         // 图片的总大小 = 帧缓存大小 * 每个像素点字节数
        NSUInteger totalBytesForImage = (int)_size.width * (int)_size.height * 4;
        // 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

        GLubyte *rawImagePixels;

        CGDataProviderRef dataProvider = NULL;
          // 判断是否支持CoreVideo的快速纹理上传 模拟器就会返回 NO
        if ([GPUImageContext supportsFastTextureUpload])
        {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
            // 图像宽度 = 每行图像数据大小 / 每个像素点字节数
            NSUInteger paddedWidthOfImage = CVPixelBufferGetBytesPerRow(renderTarget) / 4.0;
            // 图像大小 = 图像宽度 * 高度 * 每个像素点字节数
            NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4;
            // 等待OpenGL指令执行完成,与glFlush有区别
            glFinish();
            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
            [self lockForReading];
            rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget);
            // 创建CGDataProviderRef对象
            dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback);
            [[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
#else
#endif
        }
        else
        {
            // 激活帧缓存
            [self activateFramebuffer];
            rawImagePixels = (GLubyte *)malloc(totalBytesForImage);

            // 从当前的帧缓存读取图片数据
            glReadPixels(0, 0, (int)_size.width, (int)_size.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels);
            // 创建 CGDataProvider
            dataProvider = CGDataProviderCreateWithData(NULL, rawImagePixels, totalBytesForImage, dataProviderReleaseCallback);
            // 读取到数据之后不需要再持有帧缓存
            [self unlock]; // Don't need to keep this around anymore
        }

        CGColorSpaceRef defaultRGBColorSpace = CGColorSpaceCreateDeviceRGB();

        if ([GPUImageContext supportsFastTextureUpload])
        {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
          // 创建CGImage对象
            cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), defaultRGBColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, NO, kCGRenderingIntentDefault);
#else
#endif
        }
        else
        {
              // 创建CGImage对象
            cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, 4 * (int)_size.width, defaultRGBColorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaLast, dataProvider, NULL, NO, kCGRenderingIntentDefault);
        }

        // 释放数据
        // Capture image with current device orientation
        CGDataProviderRelease(dataProvider);
        CGColorSpaceRelease(defaultRGBColorSpace);

    });

    return cgImageFromBytes;
}
  • 获取帧缓存原始数据相关的方法。如果设备支持 CoreVideo框架,获取纹理数据的相关操作会调用下面的这些方法。

- (void)lockForReading
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
    if ([GPUImageContext supportsFastTextureUpload])
    {
        if (readLockCount == 0)
        {
            // 在访问CPU的像素数据之前,必须调用CVPixelBufferLockBaseAddress
            CVPixelBufferLockBaseAddress(renderTarget, 0);
        }
        readLockCount++;
    }
#endif
}

- (void)unlockAfterReading
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
    if ([GPUImageContext supportsFastTextureUpload])
    {
        NSAssert(readLockCount > 0, @"Unbalanced call to -[GPUImageFramebuffer unlockAfterReading]");
        readLockCount--;
        if (readLockCount == 0)
        {
            // 访问结束后,必须调用CVPixelBufferUnlockBaseAddress
            CVPixelBufferUnlockBaseAddress(renderTarget, 0);
        }
    }
#endif
}

- (NSUInteger)bytesPerRow;
{
    if ([GPUImageContext supportsFastTextureUpload])
    {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
        // 获取每行数据大小
        return CVPixelBufferGetBytesPerRow(renderTarget);
#else
        return _size.width * 4; // TODO: do more with this on the non-texture-cache side
#endif
    }
    else
    {
        return _size.width * 4;
    }
}

Last updated