1该博客采集后置摄像头,然后直接转UIimage显示;通过OpenGLES显示 两种
博客参考:简书loyinlin 和 IOS 官方demo:https://developer.apple.com/library/archive/samplecode/GLCameraRipple/Introduction/Intro.html
#import <UIKit/UIKit.h> #import <GLKit/GLKit.h> @interface ViewController : UIViewController @end //----------------------------------------- #import "ViewController.h" #import "CapAndRender.h" #import "CapFromCamera.h" #import <AVFoundation/AVFoundation.h> @interface ViewController ()<CapDelegate> // OpenGL ES @property (nonatomic, strong) CapAndRender *mGLView; @property (nonatomic, strong) CapCamera *mCamera; @property(nonatomic,strong)IBOutlet UIView *view1; @property(nonatomic,strong)IBOutlet UIView *RenderView; @end @implementation ViewController { dispatch_queue_t mProcessQueue; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.mGLView = [[CapAndRender alloc] initWithFrame:self.RenderView.bounds]; [self.RenderView addSubview:self.mGLView]; [self.mGLView setupGL];//着色器相关设置 //相机 self.mCamera = [[CapCamera alloc]init]; [self.mCamera InitCap]; self.mCamera.delegate = self; [self.mCamera StartCap]; __weak typeof(self) weakSelf = self; //注册回调;定义回调函数的实现 self.mCamera.cameraCB = ^(UIImage *image) { weakSelf.view1.layer.contents = (__bridge id)(image.CGImage); }; } #pragma mark - Simple Editor - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationPortrait); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } //代理方法 - (void)RenderPixelBuf:(CVPixelBufferRef *)pixelBuf { [self.mGLView displayPixelBuffer:*pixelBuf]; } @end
appdelegate:
@property (strong, nonatomic) UIWindow *window;
info.plist:添加属性
Privacy - Camera Usage Description
摄像头采集//CapFromCamera.h #import <Foundation/Foundation.h> #include <AVFoundation/AVFoundation.h> #import <UIKit/UIKit.h> #import <GLKit/GLKit.h> @protocol CapDelegate<NSObject> - (void)RenderPixelBuf:(CVPixelBufferRef*)pixelBuf; @end @interface CapCamera :NSObject @property (nonatomic, weak) id<CapDelegate>delegate;//渲染显示 //block @property (nonatomic, copy) void(^cameraCB)(UIImage *image);//直接显示采集数据 -(void)InitCap; //写在这才能在其他的.m使用的时候被找到 -(void)StartCap; -(void)StopCap; @end
#include "CapFromCamera.h" @interface CapCamera()<AVCaptureVideoDataOutputSampleBufferDelegate> @property (nonatomic, strong) AVCaptureSession *mCaptureSession; @property (nonatomic, strong) AVCaptureDeviceInput *mCaptureDeviceInput; @property (nonatomic, strong) AVCaptureVideoDataOutput *mCaptureDeviceOutput; @end @implementation CapCamera -(void)InitCap{ self.mCaptureSession = [[AVCaptureSession alloc] init]; //AVCaptureSessionPreset1920x1080 self.mCaptureSession.sessionPreset = AVCaptureSessionPreset640x480; AVCaptureDevice *inputCamera = nil; NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice *device in devices) { if ([device position] == AVCaptureDevicePositionBack) { inputCamera = device; } } self.mCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil]; if ([self.mCaptureSession canAddInput:self.mCaptureDeviceInput]) { [self.mCaptureSession addInput:self.mCaptureDeviceInput]; } self.mCaptureDeviceOutput = [[AVCaptureVideoDataOutput alloc] init]; [self.mCaptureDeviceOutput setAlwaysDiscardsLateVideoFrames:NO]; [self.mCaptureDeviceOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]]; [self.mCaptureDeviceOutput setSampleBufferDelegate:self queue:dispatch_queue_create("mProcessQueue", DISPATCH_QUEUE_SERIAL)]; if ([self.mCaptureSession canAddOutput:self.mCaptureDeviceOutput]) { [self.mCaptureSession addOutput:self.mCaptureDeviceOutput]; } AVCaptureConnection *connection = [self.mCaptureDeviceOutput connectionWithMediaType:AVMediaTypeVideo]; [connection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown]; } -(void)StartCap { [self.mCaptureSession startRunning]; } -(void)StopCap{ [self.mCaptureSession stopRunning]; } - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { CFRetain(sampleBuffer); dispatch_async(dispatch_get_main_queue(), ^{ CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); //------------------采集画面直接显示---------------- CIImage* ciImage = [CIImage imageWithCVPixelBuffer :(CVPixelBufferRef)pixelBuffer options:nil]; CIContext* context = [CIContext contextWithOptions : @{kCIContextUseSoftwareRenderer : @(YES) }]; CGRect rect = CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)); CGImageRef videoImage = [context createCGImage : ciImage fromRect : rect]; UIImage* image = [UIImage imageWithCGImage : videoImage]; //触发回调,直接显示采集数据 if (self.cameraCB) { self.cameraCB(image); } CGImageRelease(videoImage); //------------------------------------------------------- //-----------------------渲染------------------------------ if(self.delegate && [self.delegate respondsToSelector:@selector(RenderPixelBuf:)]) { [self.delegate RenderPixelBuf:&pixelBuffer]; } //------------------------------------------------------ CFRelease(sampleBuffer); }); } @end
//渲染类
#import <UIKit/UIKit.h> #import <OpenGLES/ES2/gl.h> #import <OpenGLES/ES2/glext.h> @interface CapAndRender : UIView - (void)setupGL; - (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer; @end
#import <Foundation/Foundation.h> #import <QuartzCore/QuartzCore.h> #import <AVFoundation/AVUtilities.h> #import <mach/mach_time.h> #import <GLKit/GLKit.h> #include "CapAndRender.h" // Uniform index. enum { UNIFORM_Y, UNIFORM_UV, UNIFORM_COLOR_CONVERSION_MATRIX, NUM_UNIFORMS }; GLint uniforms[NUM_UNIFORMS]; // Attribute index. enum { ATTRIB_VERTEX, ATTRIB_TEXCOORD, NUM_ATTRIBUTES }; //YUV 转 RGB // BT.601 full range (ref: http://www.equasys.de/colorconversion.html) const GLfloat kColorConversion601FullRange[] = { 1.0, 1.0, 1.0, 0.0, -0.343, 1.765, 1.4, -0.711, 0.0, }; static const GLfloat kColorConversion709[] = { 1.164, 1.164, 1.164, 0.0, -0.213, 2.112, 1.793, -0.533, 0.0, }; static const GLfloat kColorConversion601[] = { 1.164, 1.164, 1.164, 0.0, -0.392, 2.017, 1.596, -0.813, 0.0, }; @interface CapAndRender() { GLuint _program; GLuint _screenWidth;//必须是int,float会导致获取宽高的时候异常 GLuint _screenHeight; unsigned int _meshFactor; EAGLContext *_context; CVOpenGLESTextureRef _lumaTexture; CVOpenGLESTextureRef _chromaTexture; CVOpenGLESTextureCacheRef _videoTextureCache; const GLfloat *_preferredConversion; GLuint _frameBufferHandle; GLuint _colorBufferHandle; } - (void)cleanUpTextures; - (void)setupAVCapture; - (void)tearDownAVCapture; - (void)setupBuffers; - (void)setupGL; - (void)tearDownGL; - (BOOL)loadShaders; - (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file; - (BOOL)linkProgram:(GLuint)prog; @end @implementation CapAndRender + (Class)layerClass { return [CAEAGLLayer class]; } //滑屏,旋转屏幕触发 - (void)layoutSubviews { [super layoutSubviews]; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.contentScaleFactor = 2.0; // int w = frame.size.width; // int H = frame.size.height; _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; [EAGLContext setCurrentContext:_context]; CAEAGLLayer *CaeaglLayer = (CAEAGLLayer *)self.layer; CaeaglLayer.opaque = YES; CaeaglLayer.contentsScale = [UIScreen mainScreen].scale; CaeaglLayer.drawableProperties = @{ kEAGLDrawablePropertyRetainedBacking :[NSNumber numberWithBool:YES ], kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8}; // CaeaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:[NSNumber numberWithBool:YES]}; glGenFramebuffers(1, &_frameBufferHandle); glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle); glGenRenderbuffers(1, &_colorBufferHandle); glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle); [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)CaeaglLayer]; glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBufferHandle); // NSLog(@" %ld ",glGetError()); glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_screenWidth);//宽高类型不能float否则会获取不到正确的宽高,进而无法渲染 //NSLog(@" %ld ",glGetError()); glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_screenHeight); //NSLog(@" %ld ",glGetError()); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES,_screenWidth, _screenHeight); glViewport(0, 0,_screenHeight, _screenHeight); //----- //_preferredConversion = kColorConversion709; } return self; } //支持Xib,Xib中加载UIView,会调用initWithCoder - (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder]))//导入头文件,否则会出现root没有super { self.contentScaleFactor = [[UIScreen mainScreen] scale]; CAEAGLLayer *CaeaglLayer = (CAEAGLLayer *)self.layer; CaeaglLayer.opaque = TRUE; CaeaglLayer.drawableProperties = @{ kEAGLDrawablePropertyRetainedBacking :[NSNumber numberWithBool:NO], kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8}; _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; if (!_context || ![EAGLContext setCurrentContext:_context] || ![self loadShaders]) { return nil; } } return self; } # pragma mark - OpenGL setup - (void)setupGL { [EAGLContext setCurrentContext:_context]; // [self setupBuffers]; [self loadShaders]; glUseProgram(_program); glUniform1i(uniforms[UNIFORM_Y], 0); glUniform1i(uniforms[UNIFORM_UV], 1); //glUniformMatrix3fv(uniforms[UNIFORM_COLOR_CONVERSION_MATRIX], 1, GL_FALSE, _preferredConversion); // Create CVOpenGLESTextureCacheRef for optimal CVPixelBufferRef to GLES texture conversion. if (!_videoTextureCache) { CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, _context, NULL, &_videoTextureCache); if (err != noErr) { NSLog(@"Error at CVOpenGLESTextureCacheCreate %d", err); return; } } } #pragma mark - Utilities - (void)setupBuffers { _screenWidth = 0; _screenHeight = 0; glDisable(GL_DEPTH_TEST); glEnableVertexAttribArray(ATTRIB_VERTEX); glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); glEnableVertexAttribArray(ATTRIB_TEXCOORD); glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); glGenFramebuffers(1, &_frameBufferHandle); glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle); glGenRenderbuffers(1, &_colorBufferHandle); glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle); [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer]; glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_screenWidth); glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_screenHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBufferHandle); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER)); } } - (void)cleanUpTextures { if (_lumaTexture) { CFRelease(_lumaTexture); _lumaTexture = NULL; } if (_chromaTexture) { CFRelease(_chromaTexture); _chromaTexture = NULL; } // Periodic texture cache flush every frame CVOpenGLESTextureCacheFlush(_videoTextureCache, 0); } - (void)dealloc { [self cleanUpTextures]; if(_videoTextureCache) { CFRelease(_videoTextureCache); } } #pragma mark - OpenGLES drawing - (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer { CVReturn err; if (pixelBuffer != NULL) { int frameWidth = (int)CVPixelBufferGetWidth(pixelBuffer); int frameHeight = (int)CVPixelBufferGetHeight(pixelBuffer); if (!_videoTextureCache) { NSLog(@"No video texture cache"); return; } if ([EAGLContext currentContext] != _context) { [EAGLContext setCurrentContext:_context]; // 非常重要的一行代码 } [self cleanUpTextures]; //通过CVBufferGetAttachment获取pixelBuffer的颜色空间格式,决定使用的颜色转换矩阵,用于下一步的YUV到RGB颜色空间的转换; CFTypeRef colorAttachments = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL); if (colorAttachments == kCVImageBufferYCbCrMatrix_ITU_R_601_4) { if (1) { _preferredConversion = kColorConversion601FullRange; } else { _preferredConversion = kColorConversion601; } } else { _preferredConversion = kColorConversion709; } /* CVOpenGLESTextureCacheCreateTextureFromImage will create GLES texture optimally from CVPixelBufferRef. */ /* Create Y and UV textures from the pixel buffer. These textures will be drawn on the frame buffer Y-plane. */ glActiveTexture(GL_TEXTURE0); err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _videoTextureCache, pixelBuffer, NULL, GL_TEXTURE_2D, GL_LUMINANCE, frameWidth, frameHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &_lumaTexture); if (err) { NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err); } glBindTexture(CVOpenGLESTextureGetTarget(_lumaTexture), CVOpenGLESTextureGetName(_lumaTexture)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // UV-plane. glActiveTexture(GL_TEXTURE1); err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _videoTextureCache, pixelBuffer, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, frameWidth / 2, frameHeight / 2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &_chromaTexture); if (err) { NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err); } glBindTexture(CVOpenGLESTextureGetTarget(_chromaTexture), CVOpenGLESTextureGetName(_chromaTexture)); // NSLog(@"id %d", CVOpenGLESTextureGetName(_chromaTexture)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle); // Set the view port to the entire view. glViewport(0, 0, _screenWidth, _screenHeight); } glClearColor(0.1f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Use shader program. glUseProgram(_program); glUniformMatrix3fv(uniforms[UNIFORM_COLOR_CONVERSION_MATRIX], 1, GL_FALSE, _preferredConversion); // Set up the quad vertices with respect to the orientation and aspect ratio of the video. CGRect vertexSamplingRect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(_screenWidth, _screenHeight), self.layer.bounds); // Compute normalized quad coordinates to draw the frame into. CGSize normalizedSamplingSize = CGSizeMake(0.0, 0.0); CGSize cropScaleAmount = CGSizeMake(vertexSamplingRect.size.width/self.layer.bounds.size.width, vertexSamplingRect.size.height/self.layer.bounds.size.height); // Normalize the quad vertices. if (cropScaleAmount.width > cropScaleAmount.height) { normalizedSamplingSize.width = 1.0; normalizedSamplingSize.height = cropScaleAmount.height/cropScaleAmount.width; } else { normalizedSamplingSize.width = 1.0; normalizedSamplingSize.height = cropScaleAmount.width/cropScaleAmount.height; } /* The quad vertex data defines the region of 2D plane onto which we draw our pixel buffers. Vertex data formed using (-1,-1) and (1,1) as the bottom left and top right coordinates respectively, covers the entire screen. */ GLfloat quadVertexData [] = { -1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height, normalizedSamplingSize.width, -1 * normalizedSamplingSize.height, -1 * normalizedSamplingSize.width, normalizedSamplingSize.height, normalizedSamplingSize.width, normalizedSamplingSize.height, }; // 更新顶点数据 glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, quadVertexData); glEnableVertexAttribArray(ATTRIB_VERTEX); GLfloat quadTextureData[] = { // 正常坐标 0, 0, 1, 0, 0, 1, 1, 1 }; glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData); glEnableVertexAttribArray(ATTRIB_TEXCOORD); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle); if ([EAGLContext currentContext] == _context) { [_context presentRenderbuffer:GL_RENDERBUFFER]; } } #pragma mark - OpenGL ES 2 shader compilation // - (BOOL)loadShaders { GLuint vertShader, fragShader; NSURL *vertShaderURL, *fragShaderURL; _program = glCreateProgram(); // Create and compile the vertex shader. vertShaderURL = [[NSBundle mainBundle] URLForResource:@"Shader" withExtension:@"vsh"]; if (![self compileShader:&vertShader type:GL_VERTEX_SHADER URL:vertShaderURL]) { NSLog(@"Failed to compile vertex shader"); return NO; } // Create and compile fragment shader. fragShaderURL = [[NSBundle mainBundle] URLForResource:@"Shader" withExtension:@"fsh"]; if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER URL:fragShaderURL]) { NSLog(@"Failed to compile fragment shader"); return NO; } // Attach vertex shader to program. glAttachShader(_program, vertShader); // Attach fragment shader to program. glAttachShader(_program, fragShader); // Bind attribute locations. This needs to be done prior to linking. glBindAttribLocation(_program, ATTRIB_VERTEX, "position"); glBindAttribLocation(_program, ATTRIB_TEXCOORD, "texCoord"); // Link the program. if (![self linkProgram:_program]) { NSLog(@"Failed to link program: %d", _program); if (vertShader) { glDeleteShader(vertShader); vertShader = 0; } if (fragShader) { glDeleteShader(fragShader); fragShader = 0; } if (_program) { glDeleteProgram(_program); _program = 0; } return NO; } // Get uniform locations. uniforms[UNIFORM_Y] = glGetUniformLocation(_program, "SamplerY"); uniforms[UNIFORM_UV] = glGetUniformLocation(_program, "SamplerUV"); uniforms[UNIFORM_COLOR_CONVERSION_MATRIX] = glGetUniformLocation(_program, "colorConversionMatrix"); // Release vertex and fragment shaders. if (vertShader) { glDetachShader(_program, vertShader); glDeleteShader(vertShader); } if (fragShader) { glDetachShader(_program, fragShader); glDeleteShader(fragShader); } return YES; } - (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL { NSError *error; NSString *sourceString = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:&error]; if (sourceString == nil) { NSLog(@"Failed to load vertex shader: %@", [error localizedDescription]); return NO; } GLint status; const GLchar *source; source = (GLchar *)[sourceString UTF8String]; *shader = glCreateShader(type); glShaderSource(*shader, 1, &source, NULL); glCompileShader(*shader); #if defined(DEBUG) GLint logLength; glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { GLchar *log = (GLchar *)malloc(logLength); glGetShaderInfoLog(*shader, logLength, &logLength, log); NSLog(@"Shader compile log:\n%s", log); free(log); } #endif glGetShaderiv(*shader, GL_COMPILE_STATUS, &status); if (status == 0) { glDeleteShader(*shader); return NO; } return YES; } - (BOOL)linkProgram:(GLuint)prog { GLint status; glLinkProgram(prog); #if defined(DEBUG) GLint logLength; glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { GLchar *log = (GLchar *)malloc(logLength); glGetProgramInfoLog(prog, logLength, &logLength, log); NSLog(@"Program link log:\n%s", log); free(log); } #endif glGetProgramiv(prog, GL_LINK_STATUS, &status); if (status == 0) { return NO; } return YES; } - (BOOL)validateProgram:(GLuint)prog { GLint logLength, status; glValidateProgram(prog); glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { GLchar *log = (GLchar *)malloc(logLength); glGetProgramInfoLog(prog, logLength, &logLength, log); NSLog(@"Program validate log:\n%s", log); free(log); } glGetProgramiv(prog, GL_VALIDATE_STATUS, &status); if (status == 0) { return NO; } return YES; } @end