iOS自定义UIView中GLKView与CAEAGLLayer美颜渲染异常排查
iOS GLKView相机渲染美颜滤镜问题修复
问题描述
开发自定义UIView子类,基于GLKView和CAEAGLLayer实现摄像头视频捕获、美颜滤镜处理及渲染输出,当前出现以下错误:
CGContextDrawImage: invalid context 0x0. Failed to bind EAGLDrawable to GL_RENDERBUFFER 2
原实现代码如下:
#import "FUCameraPlatformView.h" #import "OverlayView.h" #import "GLView.h" #import <GLKit/GLKit.h> @interface FUCameraPlatformView() <FUCameraDelegate> @property (nonatomic, strong) EAGLContext *glContext; @property (nonatomic, strong) GLKView *glView; @property (nonatomic, strong) CIContext *ciContext; @property (nonatomic, strong) GLView *view; @property (nonatomic, strong) FUCamera *camera; @property (nonatomic, strong) MHBeautyManager *beautyManager; @property (nonatomic , strong) OverlayView *overlay; @end @implementation FUCameraPlatformView - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args cameraInstance:(FUCamera *)camera binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger beautyManager:(MHBeautyManager *)manager { self = [super init]; if (self) { _beautyManager = manager; _beautyManager.delegate = self; _camera = camera; _camera.delegate = self; _view = [[GLView alloc] initWithFrame:frame]; [self setupGL]; [self setupCamera]; } return self; } - (void)setupGL { _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; _glView = [[GLKView alloc] initWithFrame:_view.bounds context:self.glContext]; _glView.enableSetNeedsDisplay = NO; _glView.context = self.glContext; _glView.drawableDepthFormat = GLKViewDrawableDepthFormat24; _ciContext = [CIContext contextWithEAGLContext:_glContext]; // Ensure the drawable properties are set correctly CAEAGLLayer *eaglLayer = (CAEAGLLayer *)_view.layer; eaglLayer.opaque = YES; eaglLayer.contentsScale = [UIScreen mainScreen].scale; eaglLayer.drawableProperties = @{ kEAGLDrawablePropertyRetainedBacking: [NSNumber numberWithBool:NO], kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8 }; GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"Failed to make complete framebuffer object %x", status); } if (![EAGLContext setCurrentContext:self.glContext]) { NSLog(@"Failed to set current OpenGL context."); return; } [_view addSubview:_glView]; _overlay = [[OverlayView alloc] initWithFrame:_view.bounds]; _overlay.backgroundColor = [UIColor clearColor]; [_view addSubview:_overlay]; } - (void)showFaceLandmarksAndFaceRectWithPersonsArray:(NSMutableArray *)arrPersons { if (_overlay.hidden) { _overlay.hidden = NO; } _overlay.arrPersons = arrPersons; [_overlay setNeedsDisplay]; [_overlay layoutIfNeeded]; } - (void)setupCamera { [self.camera startCapture]; } - (void)didOutputVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer { // Check for null sample buffer if (!sampleBuffer) { return; } CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if (!imageBuffer) { return; } OSType formatType = CVPixelBufferGetPixelFormatType(imageBuffer); CVPixelBufferLockBaseAddress(imageBuffer, 0); CIImage *image = [CIImage imageWithCVPixelBuffer:imageBuffer]; if (!image) { CVPixelBufferUnlockBaseAddress(imageBuffer, 0); return; } CIFilter *filter = [CIFilter filterWithName:@"CIPhotoEffectChrome" keysAndValues:kCIInputImageKey, image, nil]; if (!filter) { CVPixelBufferUnlockBaseAddress(imageBuffer, 0); return; } [filter setValue:image forKey:kCIInputImageKey]; [_glView bindDrawable]; CGFloat width = _glView.drawableHeight / 640.f * 480.f; CGFloat height = _glView.drawableHeight; CGFloat x = (_glView.drawableWidth - width) / 2; if (_ciContext) { [_ciContext drawImage:filter.outputImage inRect:CGRectMake(x, 0, width, height) fromRect:CGRectMake(0, 0, 480, 640)]; } if (self.beautyManager) { [self.beautyManager processWithPixelBuffer:imageBuffer formatType:formatType]; } [_glView display]; // CVPixelBufferUnlockBaseAddress(imageBuffer, 0); } - (UIView *)view { return _view; } @end
核心问题分析
- 错误修改非渲染视图的Layer:修改了
_view.layer的CAEAGLLayer属性,但实际渲染用的是_glView(GLKView自带CAEAGLLayer),导致GLKView无法绑定正确的渲染缓冲区。 - 帧缓冲区检查时机过早:在
setupGL中调用glCheckFramebufferStatus时,GLKView尚未完成绑定,无法得到有效状态。 - 上下文未切换到当前帧处理线程:视频回调线程可能未设置正确的OpenGL上下文,导致
bindDrawable失败。 - 像素缓冲区未解锁:注释了
CVPixelBufferUnlockBaseAddress,引发内存泄漏风险。 - 美颜与滤镜处理顺序颠倒:先渲染滤镜图像再处理美颜,美颜效果无法显示到屏幕。
修复方案
1. 移除错误的Layer设置代码
删除setupGL中对_view.layer的修改,GLKView会自动管理自身的CAEAGLLayer:
- (void)setupGL { _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; _glView = [[GLKView alloc] initWithFrame:_view.bounds context:self.glContext]; _glView.enableSetNeedsDisplay = NO; _glView.drawableDepthFormat = GLKViewDrawableDepthFormat24; _ciContext = [CIContext contextWithEAGLContext:_glContext]; if (![EAGLContext setCurrentContext:self.glContext]) { NSLog(@"Failed to set current OpenGL context."); return; } [_view addSubview:_glView]; _overlay = [[OverlayView alloc] initWithFrame:_view.bounds]; _overlay.backgroundColor = [UIColor clearColor]; [_view addSubview:_overlay]; }
2. 调整帧缓冲区检查时机
将帧缓冲区检查移到bindDrawable之后,放在视频回调中:
[_glView bindDrawable]; GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"Failed to make complete framebuffer object %x", status); }
3. 确保回调线程的上下文正确
在didOutputVideoSampleBuffer开头添加上下文切换代码:
if (![EAGLContext setCurrentContext:self.glContext]) { NSLog(@"Failed to set current OpenGL context for frame processing."); CVPixelBufferUnlockBaseAddress(imageBuffer, 0); return; }
4. 恢复像素缓冲区解锁
在回调末尾添加解锁代码:
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
5. 调整美颜与滤镜处理顺序
先处理美颜,再生成CIImage应用滤镜,确保美颜效果被渲染:
if (self.beautyManager) { [self.beautyManager processWithPixelBuffer:imageBuffer formatType:formatType]; } CIImage *image = [CIImage imageWithCVPixelBuffer:imageBuffer];
修复后的完整视频回调代码
- (void)didOutputVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer { if (!sampleBuffer) return; CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if (!imageBuffer) return; OSType formatType = CVPixelBufferGetPixelFormatType(imageBuffer); CVPixelBufferLockBaseAddress(imageBuffer, 0); // 确保当前线程使用正确的OpenGL上下文 if (![EAGLContext setCurrentContext:self.glContext]) { NSLog(@"Failed to set current OpenGL context for frame processing."); CVPixelBufferUnlockBaseAddress(imageBuffer, 0); return; } // 先处理美颜 if (self.beautyManager) { [self.beautyManager processWithPixelBuffer:imageBuffer formatType:formatType]; } CIImage *image = [CIImage imageWithCVPixelBuffer:imageBuffer]; if (!image) { CVPixelBufferUnlockBaseAddress(imageBuffer, 0); return; } CIFilter *filter = [CIFilter filterWithName:@"CIPhotoEffectChrome" keysAndValues:kCIInputImageKey, image, nil]; if (!filter) { CVPixelBufferUnlockBaseAddress(imageBuffer, 0); return; } [_glView bindDrawable]; // 检查帧缓冲区状态 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"Failed to make complete framebuffer object %x", status); } CGFloat width = _glView.drawableHeight / 640.f * 480.f; CGFloat height = _glView.drawableHeight; CGFloat x = (_glView.drawableWidth - width) / 2; if (_ciContext) { [_ciContext drawImage:filter.outputImage inRect:CGRectMake(x, 0, width, height) fromRect:CGRectMake(0, 0, 480, 640)]; } [_glView display]; // 解锁像素缓冲区 CVPixelBufferUnlockBaseAddress(imageBuffer, 0); }
内容的提问来源于stack exchange,提问作者Georgina
相关产品推荐
相关产品推荐

