I am completely out of ideas.
I have the OpenGL ES 2.0 Programming Guide book, I have Apple's template OpenGL ES 2.0 code, I have followed this page: http://open.gl/drawing
I can't seem to get a simple triangle drawn, I am not sure what I am doing wrong.
Note: I have a GameViewController.xib file too that is a subclass of GLKView just like Apple's template code.
My vertex shader:
attribute vec4 position;
void main()
{
gl_Position = position;
}
My fragment shader:
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
My GameViewController Header File:
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#interface GameViewController : GLKViewController
#end
My GameViewController Implementation File:
#import "GameViewController.h"
const GLfloat vertices[] =
{
0.0f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f
};
// Class extension to keep data private
#interface GameViewController()
{
// vertex buffer object
GLuint vbo;
// vertex array object
GLuint vao;
// shader program
GLuint shaderProgram;
}
#property (strong, nonatomic) EAGLContext *context;
#property (strong, nonatomic) GLKBaseEffect *effect;
#end
#implementation GameViewController
// setup our OpenGL context
-(void)viewDidLoad
{
[super viewDidLoad];
[self setupContext];
// -------------------------------------------------------------
// create a vertex array object that will hold
// all the linking between attributes and vertex data
// -------------------------------------------------------------
[self createVertexArrayObject];
[self createVertexBufferObject];
[self loadShaders];
[self checkErrors];
}
-(void)checkErrors
{
GLenum error = glGetError();
switch (error) {
case GL_NO_ERROR:
NSLog(#"No errors");
break;
case GL_INVALID_ENUM:
NSLog(#"An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_INVALID_VALUE:
NSLog(#"A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_INVALID_OPERATION:
NSLog(#"The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
NSLog(#"The frameBuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_OUT_OF_MEMORY:
NSLog(#"There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded.");
break;
case GL_STACK_UNDERFLOW:
NSLog(#"An attempt has been made to perform an operation that would cause an internal stack to underflow.");
break;
case GL_STACK_OVERFLOW:
NSLog(#"An attempt has been made to perform an operation that would cause an internal stack to overflow.");
break;
default:
break;
}
}
#pragma mark - Setup Context -
-(void)setupContext
{
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if(!self.context)
{
NSLog(#"Failed to create OpenGL ES Context");
}
// tell our view the context is an OpenGL context
GLKView *view = (GLKView *)self.view;
view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[EAGLContext setCurrentContext:self.context];
self.effect = [[GLKBaseEffect alloc] init];
glEnable(GL_DEPTH_TEST);
}
#pragma mark - Vertex Array Object Methods -
-(void)createVertexArrayObject
{
glGenVertexArraysOES(1, &vao);
glBindVertexArrayOES(vao);
}
#pragma mark - Create Vertex Buffer Object Method -
-(void)createVertexBufferObject;
{
glGenBuffers(1, &vbo); // Generate 1 memory buffer for the VBO
// Make our VBO the current buffer object to receive data
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// -------------------------------------------------------------
// Start copying data to our VBO
//
// GL_STATIC_DRAW will upload once and drawn many times
// -------------------------------------------------------------
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
#pragma mark - Shader Code -
-(void)loadShaders
{
// load both vertex shader and fragment shader
GLuint vertexShader = [self compileVertexShader];
GLuint fragmentShader = [self compileFragmentShader];
// create a shader program from the vertex shader and fragment shader
shaderProgram = [self combineVertexShader:vertexShader AndFragmentShader:fragmentShader];
// linking vertex data and attributes
GLint positionAttrib = glGetAttribLocation(shaderProgram, "position");
glVertexAttribPointer(positionAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionAttrib);
}
-(GLuint)compileVertexShader
{
// get the path to the shader file as a C string
const GLchar *vertexShaderPath = (GLchar *)[[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"vsh"]
encoding:NSUTF8StringEncoding
error:nil] UTF8String];
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderPath, NULL);
glCompileShader(vertexShader);
// Checking if shader compiled properly
GLint status;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Vertex shader compiled correctly");
}
else
{
NSLog(#"Vertex shader compiled failed");
}
GLint logLength;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(vertexShader, logLength, &logLength, log);
NSLog(#"Vertex Shader compile log:\n%s", log);
free(log);
}
return vertexShader;
}
-(GLuint)compileFragmentShader
{
// get the path to the shader as a C string
const GLchar *fragmentShaderPath = (GLchar *)[[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"fsh"]
encoding:NSUTF8StringEncoding
error:nil] UTF8String];
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderPath, NULL);
glCompileShader(fragmentShader);
// Checking if shader compiled properly
GLint status;
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Fragment shader compiled correctly");
}
else
{
NSLog(#"Fragment shader compiled failed");
}
GLint logLength;
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(fragmentShader, logLength, &logLength, log);
NSLog(#"Fragment Shader compile log:\n%s", log);
free(log);
}
return fragmentShader;
}
-(void)linkProgram:(GLuint)paramShaderProgram
{
// link shader to program now and become active shader program
glLinkProgram(paramShaderProgram);
GLint status;
// test if the program linked correctly
glGetProgramiv(paramShaderProgram, GL_LINK_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Shader program linked correctly");
}
else
{
NSLog(#"Shader program linked failed");
}
}
-(void)validateProgram:(GLuint)paramShaderProgram
{
GLint status, logLength;
glValidateProgram(paramShaderProgram);
glGetProgramiv(paramShaderProgram, GL_VALIDATE_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Shader program validated correctly");
}
else
{
NSLog(#"Shader program validate failed");
}
glGetProgramiv(paramShaderProgram, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(paramShaderProgram, logLength, &logLength, log);
NSLog(#"Program validate log:\n%s", log);
free(log);
}
}
-(GLuint)combineVertexShader:(GLuint)paramVertexShader AndFragmentShader:(GLuint)paramFragmentShader
{
GLuint newShaderProgram = glCreateProgram();
glAttachShader(newShaderProgram, paramVertexShader);
glAttachShader(newShaderProgram, paramFragmentShader);
[self linkProgram:newShaderProgram];
[self validateProgram:newShaderProgram];
// start using shader now, will use the active shader program
glUseProgram(newShaderProgram);
return newShaderProgram;
}
#pragma mark - GLKViewController Render Delegate Methods -
-(void)setupViewport
{
glViewport(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
self.effect.transform.projectionMatrix = GLKMatrix4MakeOrtho(-3, 3, -2, 2, 1, -1);
}
-(void)update
{
[self setupViewport];
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
[self.effect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 3);
}
#pragma mark - Cleanup Memory -
-(void)tearDownContext
{
[EAGLContext setCurrentContext:self.context];
// delete vertex buffer object and vertex array object
glDeleteBuffers(1, &vbo);
glDeleteVertexArraysOES(1, &vao);
// delete shader program
if(shaderProgram)
{
glDeleteProgram(shaderProgram);
shaderProgram = 0;
}
// unset OpenGL context
if ([EAGLContext currentContext] == self.context)
{
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
}
-(void)dealloc
{
[self tearDownContext];
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if([self isViewLoaded] && self.view.window == nil)
{
self.view = nil;
[self tearDownContext];
if([EAGLContext currentContext] == self.context)
{
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
}
// Dispose of any resources that can be recreated
}
#end
All I see is my grey background screen that I cleared color to.
If anyone can point out where I went wrong, that would be great.
EDIT
I've decided to put my personal dislike for Ray's site aside and just bit the bullet, went through this tutorial:
http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial
This tutorial does not use GLKit which is an extra bonus because the learner write their own frame buffer and render buffer, present them to screen.
I feel this is closer to the OpenGL ES 2.0 pipeline than using GLKit.
As Ricardo suggested, using GLKit is more effort to understand than its worth at this point in time.
I also strongly recommend using above Ray's website tutorial link to get a working program going rather than trying to follow Apple's template code.
In my case, Apple's template code and the book had too much contradiction, causing many wasted hours.
I have also solved it using GLKit for those that are still interested in using the GLKit method.
The solution was a mix of setting my projection matrix and binding the correct vertex shader position attribute location.
Also one very important note I want to point out is:
MAKE SURE COPY BUNDLE RESOURCES FOR TARGET HAS VERTEX AND FRAGMENT SHADER!
I was at the point where the triangle was a black color and I couldn't change the color of my triangle no matter what I typed in my fragment shader file. The problem was these shader files are not copied to the bundle resource by default.
Anyhow, my working updated code is provided in full below:
#import "GameViewController.h"
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
// Uniform index.
enum
{
UNIFORM_MODELVIEWPROJECTION_MATRIX,
UNIFORM_NORMAL_MATRIX,
NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];
// Attribute index.
enum
{
ATTRIB_VERTEX,
ATTRIB_NORMAL,
NUM_ATTRIBUTES
};
const GLfloat vertices[] =
{
0.0f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f
};
// Class extension to keep data private
#interface GameViewController()
{
// vertex buffer object
GLuint vbo;
// vertex array object
GLuint vao;
// shader program
GLuint shaderProgram;
GLKMatrix4 modelViewProjectionMatrix;
GLKMatrix3 normalMatrix;
}
#property (strong, nonatomic) EAGLContext *context;
#property (strong, nonatomic) GLKBaseEffect *effect;
#end
#implementation GameViewController
// setup our OpenGL context
-(void)viewDidLoad
{
[super viewDidLoad];
[self setupContext];
[self loadShaders];
// -------------------------------------------------------------
// create a vertex array object that will hold
// all the linking between attributes and vertex data
// -------------------------------------------------------------
[self createVertexArrayObject];
[self createVertexBufferObject];
[self linkAttributes];
[self checkErrors];
}
-(void)checkErrors
{
GLenum error = glGetError();
switch (error) {
case GL_NO_ERROR:
NSLog(#"No errors");
break;
case GL_INVALID_ENUM:
NSLog(#"An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_INVALID_VALUE:
NSLog(#"A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_INVALID_OPERATION:
NSLog(#"The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
NSLog(#"The frameBuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag.");
break;
case GL_OUT_OF_MEMORY:
NSLog(#"There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded.");
break;
case GL_STACK_UNDERFLOW:
NSLog(#"An attempt has been made to perform an operation that would cause an internal stack to underflow.");
break;
case GL_STACK_OVERFLOW:
NSLog(#"An attempt has been made to perform an operation that would cause an internal stack to overflow.");
break;
default:
break;
}
}
#pragma mark - Setup Context -
-(void)setupContext
{
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if(!self.context)
{
NSLog(#"Failed to create OpenGL ES Context");
}
// tell our view the context is an OpenGL context
GLKView *view = (GLKView *)self.view;
view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[EAGLContext setCurrentContext:self.context];
self.effect = [[GLKBaseEffect alloc] init];
//glEnable(GL_DEPTH_TEST);
}
#pragma mark - Vertex Array Object Methods -
-(void)createVertexArrayObject
{
glGenVertexArraysOES(1, &vao);
glBindVertexArrayOES(vao);
}
#pragma mark - Create Vertex Buffer Object Method -
-(void)createVertexBufferObject;
{
glGenBuffers(1, &vbo); // Generate 1 memory buffer for the VBO
// Make our VBO the current buffer object to receive data
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// -------------------------------------------------------------
// Start copying data to our VBO
//
// GL_STATIC_DRAW will upload once and drawn many times
// -------------------------------------------------------------
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
-(void)linkAttributes
{
/*
// linking vertex data and attributes
GLint positionAttrib = glGetAttribLocation(shaderProgram, "position");
glVertexAttribPointer(positionAttrib, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
glEnableVertexAttribArray(positionAttrib);
*/
glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribPosition);
glBindVertexArrayOES(0);
}
#pragma mark - Shader Code -
-(void)loadShaders
{
shaderProgram = glCreateProgram();
// load both vertex shader and fragment shader
GLuint vertexShader = [self compileVertexShader];
GLuint fragmentShader = [self compileFragmentShader];
// create a shader program from the vertex shader and fragment shader
[self combineVertexShader:vertexShader AndFragmentShader:fragmentShader];
[self linkProgram:shaderProgram];
// Release vertex and fragment shaders.
if (vertexShader) {
glDetachShader(shaderProgram, vertexShader);
glDeleteShader(vertexShader);
}
if (fragmentShader) {
glDetachShader(shaderProgram, fragmentShader);
glDeleteShader(fragmentShader);
}
//NSLog(#"uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX] before = %d", uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX]);
uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX] = glGetUniformLocation(shaderProgram, "modelViewProjectionMatrix");
//NSLog(#"uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX] after = %d", uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX]);
}
-(GLuint)compileVertexShader
{
// get the path to the shader file as a C string
const GLchar *vertexShaderPath = (GLchar *)[[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"vsh"]
encoding:NSUTF8StringEncoding
error:nil] UTF8String];
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderPath, NULL);
glCompileShader(vertexShader);
// Checking if shader compiled properly
GLint status;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Vertex shader compiled correctly");
}
else
{
NSLog(#"Vertex shader compiled failed");
}
GLint logLength;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(vertexShader, logLength, &logLength, log);
NSLog(#"Vertex Shader compile log:\n%s", log);
free(log);
}
return vertexShader;
}
-(GLuint)compileFragmentShader
{
// get the path to the shader as a C string
const GLchar *fragmentShaderPath = (GLchar *)[[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"fsh"]
encoding:NSUTF8StringEncoding
error:nil] UTF8String];
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderPath, NULL);
glCompileShader(fragmentShader);
// Checking if shader compiled properly
GLint status;
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Fragment shader compiled correctly");
}
else
{
NSLog(#"Fragment shader compiled failed");
}
GLint logLength;
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(fragmentShader, logLength, &logLength, log);
NSLog(#"Fragment Shader compile log:\n%s", log);
free(log);
}
return fragmentShader;
}
-(void)combineVertexShader:(GLuint)paramVertexShader AndFragmentShader:(GLuint)paramFragmentShader
{
glAttachShader(shaderProgram, paramVertexShader);
glAttachShader(shaderProgram, paramFragmentShader);
glBindAttribLocation(shaderProgram, GLKVertexAttribPosition, "position");
//[self validateProgram:shaderProgram];
}
-(void)linkProgram:(GLuint)paramShaderProgram
{
// link shader to program now and become active shader program
glLinkProgram(paramShaderProgram);
GLint status;
// test if the program linked correctly
glGetProgramiv(paramShaderProgram, GL_LINK_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Shader program linked correctly");
}
else
{
NSLog(#"Shader program linked failed");
}
GLint logLength;
glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(shaderProgram, logLength, &logLength, log);
NSLog(#"Program link log:\n%s", log);
free(log);
}
}
-(void)validateProgram:(GLuint)paramShaderProgram
{
GLint status, logLength;
glValidateProgram(paramShaderProgram);
glGetProgramiv(paramShaderProgram, GL_VALIDATE_STATUS, &status);
if(status == GL_TRUE)
{
NSLog(#"Shader program validated correctly");
}
else
{
NSLog(#"Shader program validate failed");
}
glGetProgramiv(paramShaderProgram, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(paramShaderProgram, logLength, &logLength, log);
NSLog(#"Program validate log:\n%s", log);
free(log);
}
}
#pragma mark - GLKViewController Render Delegate Methods -
-(void)setupViewport
{
//float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
//GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 1.0f, -10.0f);
GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(-1, 1, -1, 1, 1, -1);
self.effect.transform.projectionMatrix = projectionMatrix;
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, 0.0f);
self.effect.transform.modelviewMatrix = modelViewMatrix;
modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, GL_FALSE, modelViewProjectionMatrix.m);
glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, normalMatrix.m);
}
-(void)update
{
[self setupViewport];
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArrayOES(vao);
[self.effect prepareToDraw];
glUseProgram(shaderProgram);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
#pragma mark - Cleanup Memory -
-(void)tearDownContext
{
[EAGLContext setCurrentContext:self.context];
// delete vertex buffer object and vertex array object
glDeleteBuffers(1, &vbo);
glDeleteVertexArraysOES(1, &vao);
// delete shader program
if(shaderProgram)
{
glDeleteProgram(shaderProgram);
shaderProgram = 0;
}
// unset OpenGL context
if ([EAGLContext currentContext] == self.context)
{
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
}
-(void)dealloc
{
[self tearDownContext];
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if([self isViewLoaded] && self.view.window == nil)
{
self.view = nil;
[self tearDownContext];
if([EAGLContext currentContext] == self.context)
{
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
}
// Dispose of any resources that can be recreated
}
#end
Hope that helps everyone else.
Forget about Apple's OpenGL ES 2.0 template code. If you are a beginner, there is far too much going on in there to understand properly and it mixes OpenGL ES 1.1 (GLKBaseEffect) and 2.0 (shaders).
You're better off creating an app from scratch and getting to know the pros/cons of GLKit and shaders.
This tutorial gives a great introduction to GLKit using the fixed-pipeline functionality of GLKBaseEffect:
http://www.raywenderlich.com/5223/beginning-opengl-es-2-0-with-glkit-part-1
http://www.raywenderlich.com/5235/beginning-opengl-es-2-0-with-glkit-part-2
And if you want more in-depth knowledge and learn how to use shaders in an iPhone environment, I strongly recommend this book:
http://ofps.oreilly.com/titles/9780596804824/
Ultimately, you would want to combine a GLKView to bypass all the buffer boilerplate code, and shaders to have the best control over your program.
OpenGL ES has a difficult learning curve, but once you get over the first hump things become a lot clearer.
Related
I created an iOS app using OpenGL to render yuv420p from ffmpeg.
It works fine on iPad, but on iPhone, it looks like the image below,
the picture looks italic and the right bottom triangle part like below should be on the left.
I can not found the reason, has anyone met this before?
does OpenGL on iPad and iPhone different?
below is my OpenGL view. SDL_Overlay is a struct that holds YUV plane data from ffmpeg.
#import "EAGLView.h"
// Uniform index.
enum
{
UNIFORM_Y,
UNIFORM_U,
UNIFORM_V,
NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];
// Attribute index.
enum
{
ATTRIB_VERTEX,
ATTRIB_TEXCOORD,
NUM_ATTRIBUTES
};
const GLubyte VertexIndexStruct[] = {
0, 1, 2,
2, 3, 0
};
#interface EAGLView () {
// The pixel dimensions of the CAEAGLLayer.
GLint _backingWidth;
GLint _backingHeight;
EAGLContext *m_context;
GLuint _frameBufferHandle;
GLuint _colorBufferHandle;
BOOL _pause;
}
#property GLuint program;
- (void)setupBuffers;
- (BOOL)loadShaders;
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL;
- (BOOL)linkProgram:(GLuint)prog;
- (BOOL)validateProgram:(GLuint)prog;
#end
#implementation EAGLView
+(Class) layerClass {
return [CAEAGLLayer class];
}
- (id) initWithCoder:(NSCoder*)coder {
if ((self = [super initWithCoder:coder])) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
return self;
}
-(void) applicationDidEnterBackground:(NSNotification *) notification {
_pause = YES;
}
-(void) applicationWillEnterForeground:(NSNotification *) notification {
_pause = NO;
}
-(void) destroyFrameBuffer {
// tear down GL
if (_frameBufferHandle) {
glDeleteFramebuffers(1, &_frameBufferHandle);
_frameBufferHandle = 0;
}
if (_colorBufferHandle) {
glDeleteRenderbuffers(1, &_colorBufferHandle);
_colorBufferHandle = 0;
}
if(self.program) {
glDeleteProgram(self.program);
}
}
-(void) setFrame:(CGRect)frame {
[super setFrame:frame];
[self setupGL];
}
# pragma mark - OpenGL setup
- (void)setupGL {
[self destroyFrameBuffer];
self.contentScaleFactor = [UIScreen mainScreen].scale;
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = TRUE;
[eaglLayer setContentsScale:self.contentScaleFactor];
eaglLayer.drawableProperties = #{ kEAGLDrawablePropertyRetainedBacking :[NSNumber numberWithBool:NO],
kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8};
if(m_context) {
[m_context release];
}
m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if(!m_context) {
m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
if (!m_context || ![EAGLContext setCurrentContext:m_context] || ![self loadShaders]) {
return;
}
[EAGLContext setCurrentContext:m_context];
[self setupBuffers];
[self loadShaders];
glUseProgram(self.program);
// 0 and 1 are the texture IDs of _lumaTexture and _chromaTexture respectively.
glUniform1i(uniforms[UNIFORM_Y], 0);
glUniform1i(uniforms[UNIFORM_U], 1);
glUniform1i(uniforms[UNIFORM_V], 2);
}
#pragma mark - Utilities
- (void)setupBuffers {
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);
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = TRUE;
[eaglLayer setContentsScale:self.contentScaleFactor];
[m_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
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)dealloc {
[self destroyFrameBuffer];
[super dealloc];
}
#pragma mark - OpenGL ES 2 shader compilation
- (BOOL)loadShaders {
GLuint vertShader, fragShader;
NSURL *vertShaderURL, *fragShaderURL;
// Create the shader program.
self.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(self.program, vertShader);
// Attach fragment shader to program.
glAttachShader(self.program, fragShader);
// Bind attribute locations. This needs to be done prior to linking.
glBindAttribLocation(self.program, ATTRIB_VERTEX, "position");
glBindAttribLocation(self.program, ATTRIB_TEXCOORD, "texCoord");
// Link the program.
if (![self linkProgram:self.program]) {
NSLog(#"Failed to link program: %d", self.program);
if (vertShader) {
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader) {
glDeleteShader(fragShader);
fragShader = 0;
}
if (self.program) {
glDeleteProgram(self.program);
self.program = 0;
}
return NO;
}
// Get uniform locations.
uniforms[UNIFORM_Y] = glGetUniformLocation(self.program, "SamplerY");
uniforms[UNIFORM_U] = glGetUniformLocation(self.program, "SamplerU");
uniforms[UNIFORM_V] = glGetUniformLocation(self.program, "SamplerV");
// Release vertex and fragment shaders.
if (vertShader) {
glDetachShader(self.program, vertShader);
glDeleteShader(vertShader);
}
if (fragShader) {
glDetachShader(self.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;
}
-(CGSize) renderSize {
return CGSizeMake(_backingWidth/self.contentScaleFactor, _backingHeight/self.contentScaleFactor);
}
-(void) render:(SDL_Overlay*) overlay {
if(_pause) {
return;
}
//Create Y and UV textures from the pixel buffer
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, uniforms[UNIFORM_Y]);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_LUMINANCE,
overlay->w,
overlay->h,
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
overlay->data[0]);
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);
// U-plane.
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, uniforms[UNIFORM_U]);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_LUMINANCE,
overlay->w/2,
overlay->h/2,
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
overlay->data[1]);
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);
// V-plane.
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, uniforms[UNIFORM_V]);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_LUMINANCE,
overlay->w/2,
overlay->h/2,
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
overlay->data[2]);
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);
CGFloat ratio = (CGFloat)overlay->w / overlay->h;
GLfloat actualWidth = _backingWidth;
GLfloat actualHeight = actualWidth / ratio;
if(actualHeight > _backingHeight) {
actualHeight = _backingHeight;
actualWidth = actualHeight * ratio;
}
// Set the view port to the entire view.
glViewport((_backingWidth - actualWidth) / 2,
(_backingHeight - actualHeight) / 2,
actualWidth,
actualHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Use shader program.
glUseProgram(self.program);
/*
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, -1,
1, 1,
-1, 1,
-1, -1
};
// Update attribute values.
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, quadVertexData);
glEnableVertexAttribArray(ATTRIB_VERTEX);
/*
The texture vertices are set up such that we flip the texture vertically. This is so that our top left origin buffers match OpenGL's bottom left texture coordinate system.
*/
GLfloat quadTextureData[] = {
1, 1,
1, 0,
0, 0,
0, 1,
};
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
glEnableVertexAttribArray(ATTRIB_TEXCOORD);
// glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDrawElements(GL_TRIANGLES, sizeof(VertexIndexStruct) / sizeof(VertexIndexStruct[0]), GL_UNSIGNED_BYTE, &VertexIndexStruct);
glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);
[m_context presentRenderbuffer:GL_RENDERBUFFER];
}
#end
I'm having a problem sending more than one texture to a fragment shader. Attached is a barebones test script. I'm using the GLProgram script to do the shader loading (http://iphonedevelopment.blogspot.com.au/2010/11/opengl-es-20-for-ios-chapter-4.html).
You will see in the Fragment Shader that I'm simply combining the two textures using mix, but it seems that I get the same texture for both texture1 and texture2.
// Extends GLKViewController
#import "ViewControllerImage.h"
#import <OpenGLES/ES2/glext.h>
#import "GLProgram.h"
typedef struct {
float Position[2];
float UV[2];
} VertexStruct;
const VertexStruct TestData[] = {
{{-1.0, 1.0},{0.0, 0.0}},
{{-1.0, -1.0},{0.0, 1.0}},
{{1.0, -1.0},{1.0, 1.0}}
};
const GLuint TestIndices[] = {
0,1,2
};
#implementation ViewControllerImage{
GLProgram *glProgram;
EAGLContext *context;
GLuint uvBuffer;
GLuint indexBuffer;
GLuint positionBuffer;
GLint positionID;
GLuint uvID;
GLint textureID1;
GLint textureID2;
int indiciesCount;
int vertexCount;
GLKTextureInfo *textureInfo1;
GLKTextureInfo *textureInfo2;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setupContext];
[self setupShaderProgram];
[self setupGL];
}
- (void) setupContext{
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:context];
GLKView *glkView = (GLKView *)self.view;
glkView.context = context;
}
-(void) setupShaderProgram{
glProgram = [[GLProgram alloc] initWithVertexShaderFilename:#"TestVertex" fragmentShaderFilename:#"TestFragment"];
//reguster Shader attributes
[glProgram addAttribute:#"position"];
[glProgram addAttribute:#"uvPosition"];
[glProgram addAttribute:#"texture1"];
[glProgram addAttribute:#"texture2"];
// Link Program and output log if fails
if (![glProgram link]){
NSLog(#"Failed to link");
glProgram = nil;
return;
}
[glProgram use];
//retireve attribute indicies
positionID = [glProgram attributeIndex:#"position"];
uvID = [glProgram attributeIndex:#"uvPosition"];
textureID1 = [glProgram attributeIndex:#"texture1"];
textureID2 = [glProgram attributeIndex:#"texture2"];
}
-(void) setupGL{
vertexCount = sizeof(TestData)/sizeof(TestData[0]);
indiciesCount = sizeof(TestIndices)/sizeof(TestIndices[0]);
glGenBuffers(1, &positionBuffer);
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(VertexStruct)*vertexCount, &TestData[0].Position, GL_STATIC_DRAW);
glGenBuffers(1, &uvBuffer);
glBindBuffer(GL_ARRAY_BUFFER, uvBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(VertexStruct)*vertexCount, &TestData[0].UV, GL_STATIC_DRAW);
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(TestIndices), &TestIndices[0], GL_STATIC_DRAW);
//Bind indicies
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
//Bind position and UV buffers
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glVertexAttribPointer(positionID, 2, GL_FLOAT, GL_FALSE, sizeof(VertexStruct), 0);
glEnableVertexAttribArray(positionID);
glBindBuffer(GL_ARRAY_BUFFER, uvBuffer);
glVertexAttribPointer(uvID, 2, GL_FLOAT, GL_FALSE, sizeof(VertexStruct), 0);
glEnableVertexAttribArray(uvID);
//Load textures
NSError *error;
textureInfo1 = [GLKTextureLoader textureWithCGImage:[[UIImage imageNamed:#"image1.png"] CGImage] options:nil error:&error];
if(error.description) NSLog(#"Texture 1 error: %#", error.description);
textureInfo2 = [GLKTextureLoader textureWithCGImage:[[UIImage imageNamed:#"image2.png"] CGImage] options:nil error:&error];
if(error.description) NSLog(#"Texture 2 error: %#", error.description);
//Bind Textures
glActiveTexture(GL_TEXTURE0);
glBindTexture(textureInfo1.target, textureInfo1.name);
glUniform1i( textureID1, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(textureInfo2.target, textureInfo2.name);
glUniform1i( textureID2, 1);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear( GL_COLOR_BUFFER_BIT );
glDrawElements(GL_TRIANGLES, indiciesCount, GL_UNSIGNED_INT, 0);
}
#end
VertexShader:
precision mediump float;
attribute vec4 position;
attribute vec2 uvPosition;
varying vec2 uv;
void main(void){
uv = uvPosition;
gl_Position = position;
}
FragmentShader
precision mediump float;
uniform sampler2D texture1;
uniform sampler2D texture2;
varying vec2 uv;
void main(void){
gl_FragColor = mix(texture2D(texture1, uv),texture2D(texture2, uv),0.5);
}
I would really appreciate it if someone could help me with this problem I'm having. I'm sure it must be simple. I've also checked GL_MAX_TEXTURE_IMAGE_UNITS which returns 8.
Oh Snap! I figured it out. I was accessing the attribute and uniform locations incorrectly. Attributes use glBindAttribLocation and uniforms use glGetUniformLocation I'm using the GLProgram class which use convenience methods addAttribute / attributeIndex and uniformIndex
This is the offending code:
-(void) setupShaderProgram{
glProgram = [[GLProgram alloc] initWithVertexShaderFilename:#"TestVertex" fragmentShaderFilename:#"TestFragment"];
//reguster Shader attributes
[glProgram addAttribute:#"position"];
[glProgram addAttribute:#"uvPosition"];
[glProgram addAttribute:#"texture1"]; //wrong - this is not an attribute
[glProgram addAttribute:#"texture2"]; //wrong - this is not an attribute
// Link Program and output log if fails
if (![glProgram link]){
NSLog(#"Failed to link");
glProgram = nil;
return;
}
[glProgram use];
//retireve attribute indicies
positionID = [glProgram attributeIndex:#"position"];
uvID = [glProgram attributeIndex:#"uvPosition"];
textureID1 = [glProgram attributeIndex:#"texture1"]; //Wrong - this is a uniform
textureID2 = [glProgram attributeIndex:#"texture2"];//Wrong - this is a uniform
}
And this is what it should be:
-(void) setupShaderProgram{
glProgram = [[GLProgram alloc] initWithVertexShaderFilename:#"EmptyVertex" fragmentShaderFilename:#"EmptyFragment"];
//register Shader attributes
[glProgram addAttribute:#"position"];
[glProgram addAttribute:#"uvPosition"];
// Link Program and output log if fails
if (![glProgram link]){
NSLog(#"Failed to link");
glProgram = nil;
return;
}
[glProgram use];
//retrieve attribute indicies
positionID = [glProgram attributeIndex:#"position"];
uvID = [glProgram attributeIndex:#"uvPosition"];
//retrieve uniform indicies
textureID1 = [glProgram uniformIndex:#"texture1"];
textureID2 = [glProgram uniformIndex:#"texture2"];
}
I'm such a noob.
Okay, I think I have solved it. I need to start the texture unit indexing at 1 not 0. Otherwise TEXTURE1 is the same texture as TEXTURE0 !!
Incorrect:
glActiveTexture(GL_TEXTURE0);
glBindTexture(textureInfo1.target, textureInfo1.name);
glUniform1i(textureID1, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(textureInfo2.target, textureInfo2.name);
glUniform1i( textureID2, 1);
Correct:
glActiveTexture(GL_TEXTURE1);
glBindTexture(textureInfo1.target, textureInfo1.name);
glUniform1i(textureID1, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(textureInfo2.target, textureInfo2.name);
glUniform1i( textureID2, 2);
Weird.
Basically, in my iOS app, i have a modal screen, in which i display some OpenGL ES graphics. I am able to enter and exit this modal screen 6 times, before the application crashes. I'm assuming this is a memory issue, but i'm stuck as to what and / or where.
Any help on this matter is incredibly appreciated.
In my OGLViewController:
#define OPENGL_ERROR_CHECK {GLuint error = glGetError(); ((error == GL_NO_ERROR) ? : NSLog(#"GL Error: %d", (error)));}
#interface OGLItemViewController : GLKViewController
#property (nonatomic) GLuint program;
// item ogl arrays + buffers
#property (nonatomic) NSInteger numOGLBuffers;
#property (nonatomic) GLuint* vertexArrays;
#property (nonatomic) GLuint* vertexBuffers;
// index buffer for ogl item vertices
#property (nonatomic) GLuint* indexBuffers;
#property (strong, nonatomic) EAGLContext *context;
#property (strong, nonatomic) GLKBaseEffect *effect;
#end
#implementation
- (void)viewDidLoad
{
[super viewDidLoad];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
OPENGL_ERROR_CHECK
glFinish(); // put this in here to make sure all previous calls have been finished
OPENGL_ERROR_CHECK
if (!self.context)
{
NSLog(#"Failed to create ES context");
}
GLKView *view = (GLKView *) self.view;
view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
view.opaque = NO;
self.numOGLBuffers = 0;
self.vertexArrays = nil;
self.vertexBuffers = nil;
self.indexBuffers = nil;
}
- (void)setupGL
{
OPENGL_ERROR_CHECK
[EAGLContext setCurrentContext:self.context];
OPENGL_ERROR_CHECK
[self loadShaders];
OPENGL_ERROR_CHECK
self.effect = [[GLKBaseEffect alloc] init];
OPENGL_ERROR_CHECK
self.effect.light0.enabled = GL_TRUE;
OPENGL_ERROR_CHECK
self.effect.colorMaterialEnabled = GL_TRUE;
OPENGL_ERROR_CHECK
self.effect.lightModelTwoSided = GL_FALSE;
OPENGL_ERROR_CHECK
self.effect.light0.diffuseColor = GLKVector4Make(0.69f, 0.69f, 0.69f, 0.5f);
OPENGL_ERROR_CHECK
glEnable(GL_DEPTH_TEST);
OPENGL_ERROR_CHECK
Item *item = [GlobalStore sharedInstance].item
NSMutableArray *shells = item.geometry;
if (shells.count > 0)
{
_vertexArrays = malloc(shells.count * sizeof(GLuint));
_vertexBuffers = malloc(shells.count * sizeof(GLuint));
_indexBuffers = malloc(shells.count * sizeof(GLuint));
self.numOGLBuffers = shells.count;
for (int i = 0; i < shells.count; i++)
{
Geometry *geom = [shells objectAtIndex:i];
if (geom.vertexCount > 0)
{
GLuint vao = 0;
OPENGL_ERROR_CHECK
glGenVertexArraysOES(1, &vao);
OPENGL_ERROR_CHECK
glBindVertexArrayOES(vao);
OPENGL_ERROR_CHECK
_vertexArrays[i] = vao;
if (!geom.vertices)
{
[self displayError:-998]; // generic error codes that i've just canned in to see if any problems with these pointers
}
if (!geom.indices)
{
[self displayError:-997];
}
// create vertice buffer
GLuint vbo = 0;
glGenBuffers(1, &vbo);
OPENGL_ERROR_CHECK
glBindBuffer(GL_ARRAY_BUFFER, vbo);
OPENGL_ERROR_CHECK
glBufferData(GL_ARRAY_BUFFER, geom.vertexCount * sizeof(OGLVertices), geom.vertices, GL_STATIC_DRAW);
OPENGL_ERROR_CHECK
_vertexBuffers[i] = vbo;
// create index by buffer
GLuint ibo = 0;
glGenBuffers(1, &ibo);
OPENGL_ERROR_CHECK
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
OPENGL_ERROR_CHECK
glBufferData(GL_ELEMENT_ARRAY_BUFFER, geom.indexCount * sizeof(GLuint), geom.indices, GL_STATIC_DRAW);
OPENGL_ERROR_CHECK
_indexBuffers[i] = ibo;
// enable position, normal and colour attributes
glEnableVertexAttribArray(GLKVertexAttribPosition);
OPENGL_ERROR_CHECK
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(OGLVertices), (const GLvoid *) offsetof(OGLVertices, Position));
OPENGL_ERROR_CHECK
glEnableVertexAttribArray(GLKVertexAttribNormal);
OPENGL_ERROR_CHECK
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, sizeof(OGLVertices), (const GLvoid *) offsetof(OGLVertices, Normal));
OPENGL_ERROR_CHECK
glEnableVertexAttribArray(GLKVertexAttribColor);
OPENGL_ERROR_CHECK
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(OGLVertices), (const GLvoid *) offsetof(OGLVertices, Colour));
OPENGL_ERROR_CHECK
}
}
glBindVertexArrayOES(0);
OPENGL_ERROR_CHECK
}
}
- (void)tearDownGL
{
[EAGLContext setCurrentContext:self.context];
self.effect = nil;
[self deleteOGLData];
}
- (void)dealloc
{
[self tearDownGL];
if ([EAGLContext currentContext] == self.context)
{
[EAGLContext setCurrentContext:nil];
}
}
- (void) viewDidDisappear:(BOOL)animated
{
[self tearDownGL];
}
- (void)deleteOGLData
{
// delete ogl buffers and arrays
if (self.numOGLBuffers > 0)
{
if (_vertexBuffers)
{
OPENGL_ERROR_CHECK
glDeleteBuffers(self.numOGLBuffers, _vertexBuffers);
OPENGL_ERROR_CHECK
free(_vertexBuffers);
_vertexBuffers = nil;
}
if (_vertexArrays)
{
glDeleteVertexArraysOES(self.numOGLBuffers, _vertexArrays);
OPENGL_ERROR_CHECK
free(_vertexArrays);
_vertexArrays = nil;
}
if (_indexBuffers)
{
glDeleteBuffers(self.numOGLBuffers, _indexBuffers);
OPENGL_ERROR_CHECK
free(_indexBuffers);
_indexBuffers = nil;
}
}
self.numOGLBuffers = 0;
glDeleteProgram(self.program);
OPENGL_ERROR_CHECK
self.program = 0;
glFinish(); // again, just put this in here to check everything has finished
OPENGL_ERROR_CHECK
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
OPENGL_ERROR_CHECK
glClearColor(1.0, 1.0, 1.0, 0.0);
OPENGL_ERROR_CHECK
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
OPENGL_ERROR_CHECK
// Render the object with GLKit
[self.effect prepareToDraw];
OPENGL_ERROR_CHECK
NSMutableArray *shells = [GlobalStore sharedInstance].item.geometry;
if (shells && _vertexArrays && _vertexBuffers)
{
for (int i = 0; i < shells.count; i++)
{
Geometry *geom = [shells objectAtIndex:i];
if (geom.vertexCount > 0)
{
GLuint vao = self.vertexArrays[i];
glBindVertexArrayOES(vao);
OPENGL_ERROR_CHECK
if(geom.indexCount == 0)
[self displayError:-996];
glDrawElements(GL_TRIANGLES, geom.indexCount, GL_UNSIGNED_INT, 0);
OPENGL_ERROR_CHECK
self.initialised = YES;
}
}
}
}
- (BOOL)loadShaders
{
GLuint vertShader, fragShader;
NSString *vertShaderPathname, *fragShaderPathname;
// Create shader program.
self.program = glCreateProgram();
OPENGL_ERROR_CHECK
// Create and compile vertex shader.
vertShaderPathname = [[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname])
{
NSLog(#"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
fragShaderPathname = [[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname])
{
NSLog(#"Failed to compile fragment shader");
return NO;
}
// Attach vertex shader to program.
glAttachShader(_program, vertShader);
OPENGL_ERROR_CHECK
// Attach fragment shader to program.
glAttachShader(_program, fragShader);
OPENGL_ERROR_CHECK
// Bind attribute locations.
// This needs to be done prior to linking.
glBindAttribLocation(_program, GLKVertexAttribPosition, "position");
OPENGL_ERROR_CHECK
glBindAttribLocation(_program, GLKVertexAttribNormal, "normal");
OPENGL_ERROR_CHECK
// Link program.
if (![self linkProgram:_program])
{
NSLog(#"Failed to link program: %d", _program);
if (vertShader)
{
glDeleteShader(vertShader);
OPENGL_ERROR_CHECK
vertShader = 0;
}
if (fragShader)
{
glDeleteShader(fragShader);
OPENGL_ERROR_CHECK
fragShader = 0;
}
if (_program)
{
glDeleteProgram(_program);
OPENGL_ERROR_CHECK
_program = 0;
}
return NO;
}
// Get uniform locations.
uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX] = glGetUniformLocation(_program, "modelViewProjectionMatrix");
uniforms[UNIFORM_NORMAL_MATRIX] = glGetUniformLocation(_program, "normalMatrix");
// Release vertex and fragment shaders.
if (vertShader)
{
glDetachShader(_program, vertShader);
OPENGL_ERROR_CHECK
glDeleteShader(vertShader);
OPENGL_ERROR_CHECK
}
if (fragShader)
{
glDetachShader(_program, fragShader);
OPENGL_ERROR_CHECK
glDeleteShader(fragShader);
OPENGL_ERROR_CHECK
}
return YES;
}
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
GLint status;
const GLchar *source;
source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
if (!source)
{
NSLog(#"Failed to load vertex shader");
return NO;
}
*shader = glCreateShader(type);
OPENGL_ERROR_CHECK
glShaderSource(*shader, 1, &source, NULL);
OPENGL_ERROR_CHECK
glCompileShader(*shader);
OPENGL_ERROR_CHECK
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
OPENGL_ERROR_CHECK
if (status == 0)
{
glDeleteShader(*shader);
OPENGL_ERROR_CHECK
return NO;
}
return YES;
}
- (BOOL)linkProgram:(GLuint)prog
{
GLint status;
glLinkProgram(prog);
OPENGL_ERROR_CHECK
glGetProgramiv(prog, GL_LINK_STATUS, &status);
OPENGL_ERROR_CHECK
if (status == 0)
{
return NO;
}
return YES;
}
- (BOOL)validateProgram:(GLuint)prog
{
GLint logLength, status;
glValidateProgram(prog);
OPENGL_ERROR_CHECK
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
OPENGL_ERROR_CHECK
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
OPENGL_ERROR_CHECK
NSLog(#"Program validate log:\n%s", log);
free(log);
}
glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
OPENGL_ERROR_CHECK
if (status == 0)
{
return NO;
}
return YES;
}
-(void)displayError: (GLuint) err
{
NSString *msg = [[NSString alloc] initWithFormat:#"OpenGL Error: %d", err];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"Error"
message: msg
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
#end
Now - i don't know if it's the right way to do this, but in the viewdiddisappear method, i make sure to delete all the ogl data. i wanted to make sure that when i exited the view, the ogl data is deleted from memory.
So, if i enter the modal view, exit, and repeat 5 more times, i will get a black screen crash, and will break, with the following screen:
I have made sure everything is done in a single thread where appropriate. I have tried to make sure all objects and buffers are deleted.
So i'm wondering what on earth am i doing wrong to cause it to suddenly crash..... Nothing in the profiler seems to indicate anything. I don't seem to have any memory leaks or anything too substantial that would cause memory to run out.
( i do know i'm rendering every frame, when i dont need to - i will address this at a later point.)
The globalstore i have holds an instance of some objects that i use. These should always be valid (i've checked).
What i needed to do, was to delete the GLKView drawables.
In the ViewDidDisappear method (might not be the proper place, but it works) add the following:
GLKView *view = (GLKView*) self.view;
[view deleteDrawable];
I've been fighting for a week off and on to save out my opengl renderings (which I'm using for green screening) to video via an AVAssetWriter.
I have created a simple rig below to show what I'm doing.
I've asked on apple forums and received advice on the process, which is also described here:
allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/ and is used in the GPUImage library.
To my knowledge I am doing the exact same thing - I even use the method from GPUImage to create the FBO.
I have verified that the drawing is okay (I have drawing methods in this code too; which are disabled),
The FBO is created okay and returns success for : glCheckFramebufferStatus
There are no crashes, No exceptions, no warnings, the writer is in fine status, all texturecaches, buffers, etc are created without error.
However I still get BLACK for my video output.
If I set my glClear to white, then I get a white rectangle which is not at the video size I requested.
I never get my triangle rendere into my video.
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "TestViewController.h"
/////////////////////////////////////////////////////////////////
// This data type is used to store information for each vertex
typedef struct
{
GLKVector3 positionCoords;
}
SceneVertex;
/////////////////////////////////////////////////////////////////
// Define vertex data for a triangle to use in example
static const SceneVertex vertices[] =
{
{{-1.0f, -1.0f, 1.0}}, // lower left corner
{{1.0f, -1.0f, 0.5}}, // lower right corner
{{1.0f, 1.0f, 0.0}} // upper left corner
};
#interface TestViewController ()
#property(nonatomic, readwrite, assign) CVOpenGLESTextureCacheRef videoTextureCache;
#property(strong, nonatomic) GLKTextureInfo *background;
#property(nonatomic, strong) AVAssetWriter *assetWriter;
#property(nonatomic) BOOL isRecording;
#property(nonatomic, strong) AVAssetWriterInput *assetWriterVideoInput;
#property(nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *assetWriterPixelBufferInput;
#property(nonatomic, assign) CFAbsoluteTime startTime;
#property(nonatomic, strong) GLKView *glkView;
#property(nonatomic, strong) GLKBaseEffect *screenGLEffect;
#property(nonatomic, strong) GLKBaseEffect *FBOGLEffect;
#property(nonatomic, strong) NSTimer *recordingTimer;
- (BOOL)isRetina;
#end
#implementation TestViewController
{
CVOpenGLESTextureCacheRef _writerTextureCache;
GLuint _writerRenderFrameBuffer;
GLuint vertexBufferID;
EAGLContext *_writerContext;
CVOpenGLESTextureRef _writerTexture;
}
- (GLKBaseEffect *)createBasicDrawingEffectInCurrentContext
{
GLKBaseEffect *basicGLEffect = [[GLKBaseEffect alloc] init];
basicGLEffect.useConstantColor = GL_TRUE;
basicGLEffect.constantColor = GLKVector4Make(
.5f, // Red
1.0f, // Green
.5f, // Blue
1.0f);// Alpha
// Set the background color stored in the current context
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // background color
// Generate, bind, and initialize contents of a buffer to be
// stored in GPU memory
glGenBuffers(1, // STEP 1
&vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, // STEP 2
vertexBufferID);
glBufferData( // STEP 3
GL_ARRAY_BUFFER, // Initialize buffer contents
sizeof(vertices), // Number of bytes to copy
vertices, // Address of bytes to copy
GL_STATIC_DRAW); // Hint: cache in GPU memory
return basicGLEffect;
}
/////////////////////////////////////////////////////////////////
//
- (void)viewDidUnload
{
[super viewDidUnload];
// Make the view's context current
GLKView *view = (GLKView *) self.view;
[EAGLContext setCurrentContext:view.context];
// Stop using the context created in -viewDidLoad
((GLKView *) self.view).context = nil;
[EAGLContext setCurrentContext:nil];
//////////////////////////////////////////////////////////////
#pragma mark AVWriter setup
//////////////////////////////////////////////////////////////
- (NSString *)tempFilePath
{
return [NSHomeDirectory() stringByAppendingPathComponent:#"Documents/output2.m4v"];
}
- (void)removeTempFile
{
NSString *path = [self tempFilePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL exists = [fileManager fileExistsAtPath:path];
NSLog(#">>>remove %# Exists %d", path, exists);
NSError *error;
unlink([path UTF8String]);
NSLog(#">>>AFTER REMOVE %# Exists %d %#", path, exists, error);
}
- (void)createWriter
{
//My setup code is based heavily on the GPUImage project, https://github.com/BradLarson/GPUImage so some of these dictionary names and structure are similar to the code from that project - I recommend you check it out if you are interested in Video filtering/recording
[self removeTempFile];
NSError *error;
self.assetWriter = [[AVAssetWriter alloc]
initWithURL:[NSURL fileURLWithPath:[self tempFilePath]]
fileType:AVFileTypeQuickTimeMovie
error:&error];
if (error)
{
NSLog(#"Couldn't create writer, %#", error.localizedDescription);
return;
}
NSDictionary *outputSettings = #{
AVVideoCodecKey : AVVideoCodecH264,
AVVideoWidthKey : #640,
AVVideoHeightKey : #480
};
self.assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:outputSettings];
self.assetWriterVideoInput.expectsMediaDataInRealTime = YES;
NSDictionary *sourcePixelBufferAttributesDictionary = #{(id) kCVPixelBufferPixelFormatTypeKey : #(kCVPixelFormatType_32BGRA),
(id) kCVPixelBufferWidthKey : #640,
(id) kCVPixelBufferHeightKey : #480};
self.assetWriterPixelBufferInput = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.assetWriterVideoInput
sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
self.assetWriterVideoInput.transform = CGAffineTransformMakeScale(1, -1);
if ([_assetWriter canAddInput:self.assetWriterVideoInput])
{
[_assetWriter addInput:self.assetWriterVideoInput];
} else
{
NSLog(#"can't add video writer input %#", self.assetWriterVideoInput);
}
/*
_assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:nil];
if ([_assetWriter canAddInput:_assetWriterAudioInput]) {
[_assetWriter addInput:_assetWriterAudioInput];
_assetWriterAudioInput.expectsMediaDataInRealTime = YES;
}
*/
}
- (void)writeMovieToLibraryWithPath:(NSURL *)path
{
NSLog(#"writing %# to library", path);
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:path
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error)
{
NSLog(#"Error saving to library%#", [error localizedDescription]);
} else
{
NSLog(#"SAVED %# to photo lib", path);
}
}];
}
//////////////////////////////////////////////////////////////
#pragma mark touch handling
//////////////////////////////////////////////////////////////
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if (self.isRecording)
{
[self finishRecording];
} else
{
[self startRecording];
}
}
//////////////////////////////////////////////////////////////
#pragma mark recording
//////////////////////////////////////////////////////////////
- (void)startRecording;
{
NSLog(#"started recording");
#warning debugging startrecording
// NSLog(#"bypassing usual write method");
// if (![assetWriter startWriting]){
// NSLog(#"writer not started %#, %d", assetWriter.error, assetWriter.status);
// }
self.startTime = CFAbsoluteTimeGetCurrent();
[self createWriter];
[self.assetWriter startWriting];
[self.assetWriter startSessionAtSourceTime:kCMTimeZero];
NSAssert([self.assetWriterPixelBufferInput pixelBufferPool], #"writerpixelbuffer input has no pools");
if (!_writerContext)
{
_writerContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!_writerContext || ![EAGLContext setCurrentContext:_writerContext])
{
NSLog(#"Problem with OpenGL context.");
return;
}
}
[EAGLContext setCurrentContext:_writerContext];
NSLog(#"Creating FBO");
[self createDataFBOUsingGPUImagesMethod];
// [self createDataFBO];
self.isRecording = YES;
NSLog(#"Recording is started");
self.recordingTimer = [NSTimer scheduledTimerWithTimeInterval:1 / 30
target:self
selector:#selector(tick:)
userInfo:nil repeats:YES];
}
- (void)tick:(id)tick
{
[self drawBasicGLTOFBOForWriting];
}
- (void)finishRecording;
{
[self.recordingTimer invalidate];
self.recordingTimer = nil;
NSLog(#"finished recording");
if (self.assetWriter.status == AVAssetWriterStatusCompleted || !self.isRecording)
{
NSLog(#"already completed ingnoring");
return;
}
NSLog(#"Asset writer writing");
self.isRecording = NO;
// runOnMainQueueWithoutDeadlocking(^{
NSLog(#"markng inputs as finished");
//TODO - these cause an error
[self.assetWriterVideoInput markAsFinished];
__weak TestViewController *blockSelf = self;
[self.assetWriter finishWritingWithCompletionHandler:^{
if (self.assetWriter.error == nil)
{
NSLog(#"saved ok - writing to lib");
[self writeMovieToLibraryWithPath:[NSURL fileURLWithPath:[self tempFilePath]]];
} else
{
NSLog(#" did not save due to error %#", self.assetWriter.error);
}
}];
// });
}
- (void)drawBasicGLTOFBOForWriting
{
if (!self.isRecording)
{
return;
}
[EAGLContext setCurrentContext:_writerContext];
if (!self.FBOGLEffect)
{
self.FBOGLEffect = [self createBasicDrawingEffectInCurrentContext];
}
glDisable(GL_DEPTH_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, _writerRenderFrameBuffer);
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
[self.FBOGLEffect prepareToDraw];
// Clear Frame Buffer (erase previous drawing)
// Enable use of positions from bound vertex buffer
glEnableVertexAttribArray( // STEP 4
GLKVertexAttribPosition);
glVertexAttribPointer( // STEP 5
GLKVertexAttribPosition,
3, // three components per vertex
GL_FLOAT, // data is floating point
GL_FALSE, // no fixed point scaling
sizeof(SceneVertex), // no gaps in data
NULL); // NULL tells GPU to start at
// beginning of bound buffer
// Draw triangles using the first three vertices in the
// currently bound vertex buffer
glDrawArrays(GL_TRIANGLES, // STEP 6
0, // Start with first vertex in currently bound buffer
3); // Use three vertices from currently bound buffer
glFlush();
CFAbsoluteTime interval = (CFAbsoluteTimeGetCurrent() - self.startTime) * 1000;
CMTime currentTime = CMTimeMake((int) interval, 1000);
[self writeToFileWithTime:currentTime];
}
- (void)writeToFileWithTime:(CMTime)time
{
if (!self.assetWriterVideoInput.readyForMoreMediaData)
{
NSLog(#"Had to drop a video frame");
return;
}
if (kCVReturnSuccess == CVPixelBufferLockBaseAddress(_writerPixelBuffer,
kCVPixelBufferLock_ReadOnly))
{
uint8_t *pixels = (uint8_t *) CVPixelBufferGetBaseAddress(_writerPixelBuffer);
// process pixels how you like!
BOOL success = [self.assetWriterPixelBufferInput appendPixelBuffer:_writerPixelBuffer
withPresentationTime:time];
NSLog(#"wrote at %# : %#", CMTimeCopyDescription(NULL, time), success ? #"YES" : #"NO");
CVPixelBufferUnlockBaseAddress(_writerPixelBuffer, kCVPixelBufferLock_ReadOnly);
}
}
//////////////////////////////////////////////////////////////
#pragma mark FBO setup
//////////////////////////////////////////////////////////////
- (void)createDataFBOUsingGPUImagesMethod;
{
glActiveTexture(GL_TEXTURE1);
glGenFramebuffers(1, &_writerRenderFrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _writerRenderFrameBuffer);
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, _writerContext, NULL, &_writerTextureCache);
if (err)
{
NSAssert(NO, #"Error at CVOpenGLESTextureCacheCreate %d", err);
}
// Code originally sourced from http://allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/
CVPixelBufferPoolCreatePixelBuffer(NULL, [self.assetWriterPixelBufferInput pixelBufferPool], &_writerPixelBuffer);
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _writerTextureCache, _writerPixelBuffer,
NULL, // texture attributes
GL_TEXTURE_2D,
GL_RGBA, // opengl format
480,
320,
GL_BGRA, // native iOS format
GL_UNSIGNED_BYTE,
0,
&_writerTexture);
if (err)
{
NSAssert(NO, #"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
}
glBindTexture(CVOpenGLESTextureGetTarget(_writerTexture), CVOpenGLESTextureGetName(_writerTexture));
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(_writerTexture), 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
NSAssert(status == GL_FRAMEBUFFER_COMPLETE, #"Incomplete filter FBO: %d", status);
}
#end
Four possibilities jump to mind:
Your Viewport isn't the right size/shape/in the right place. Try calling glViewport somewhere before drawing anything.
Your shader is broken. I see you don't have any kind of shader setup, so you might need to add a basic passthrough vertex and fragment shader pair that just multiplies position by perspective and modelview matrix, and draws using vertex color, or a fixed color.
Your Projection matrix isn't good. Try using a basic orthographic matrix at first.
Your Modelview matrix isn't good. If you can animate something, try starting with the identity matrix and then slowly rotating it through first the X axis then the Y axis.
Make sure _writerpixelbuffer is not NULL.
I'm trying to hack the generic Xcode iOS OpenGL Game template to draw two vertex buffer objects and render them with different GLSL shaders.
I 'think' I'm rendering the two VBOs correctly? (Because I see them both when running both VBOs through the first shader program) However, my second shader does not appear to be rendering my second object at all.
Here is the vertex data for the two squares:
GLfloat gCubeVertexData[36] =
{
// Data layout for each line below is:
// positionX, positionY, positionZ, normalX, normalY, normalZ,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
GLfloat fooVertexData[36] =
{
// Data layout for each line below is:
// positionX, positionY, positionZ, normalX, normalY, normalZ
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
Here is where I am trying to generate two VBOs and bind them to the data. Not sure what the purpose of the 'glBindVertexArrayOES(0)' is at the end though?:
- (void)setupGL
{
[EAGLContext setCurrentContext:self.context];
[self loadShaders];
//---- First Vertex Array Object --------
glGenVertexArraysOES(1, &_vertexArray1);
glGenBuffers(1, &_vertexBuffer1);
glBindVertexArrayOES(_vertexArray1);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer1);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData), gCubeVertexData, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
//----- Second Vertex Array Object ----------
glGenVertexArraysOES(1, &_vertexArray2);
glGenBuffers(1, &_vertexBuffer2);
glBindVertexArrayOES(_vertexArray2);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2);
glBufferData(GL_ARRAY_BUFFER, sizeof(fooVertexData), fooVertexData, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArrayOES(0);
}
I'm using this update code to animate the model-view-projection matrixes:
- (void)update
{
_rotation += self.timeSinceLastUpdate * 0.2f;
float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(-1.0f, 1.0f, -1.0f / aspect, 1.0f / aspect, -10.0f, 10.0f);
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.5f, 0.0f, 0.0f);
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, GLKMatrix4MakeZRotation(0.0 - _rotation));
_modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
GLKMatrix4 modelViewMatrix2 = GLKMatrix4MakeTranslation(-0.5f, 0.0f, 0.0f);
modelViewMatrix2 = GLKMatrix4Multiply(modelViewMatrix2, GLKMatrix4MakeZRotation(_rotation));
_modelViewProjectionMatrix2 = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix2);
}
When I call the '_program2' shader I don't see the second square:
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArrayOES(_vertexArray1);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer1);
glUseProgram(_program);
glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m);
glDrawArrays(GL_TRIANGLES, 0, 6);
///////// second object and shader program:
glBindVertexArrayOES(_vertexArray2);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2);
glUseProgram(_program2);
glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX2], 1, 0, _modelViewProjectionMatrix2.m);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
I've basically tried duplicating the code for loading the first shader, to load the second. I suspect I may be doing something wrong here.. but I'm not sure what:
- (BOOL)loadShaders
{
GLuint vertShader, fragShader, vertShader2, fragShader2;
NSString *vertShaderPathname, *fragShaderPathname, *vertShaderPathname2, *fragShaderPathname2;
// Create shader program.
_program = glCreateProgram();
// Create and compile vertex shader.
vertShaderPathname = [[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
NSLog(#"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
fragShaderPathname = [[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
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");
// Link 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_MODELVIEWPROJECTION_MATRIX] = glGetUniformLocation(_program, "modelViewProjectionMatrix");
// Release vertex and fragment shaders.
if (vertShader) {
glDetachShader(_program, vertShader);
glDeleteShader(vertShader);
}
if (fragShader) {
glDetachShader(_program, fragShader);
glDeleteShader(fragShader);
}
///////////////// the second shader:
_program2 = glCreateProgram();
vertShaderPathname2 = [[NSBundle mainBundle] pathForResource:#"Shader2" ofType:#"vsh"];
if (![self compileShader:&vertShader2 type:GL_VERTEX_SHADER file:vertShaderPathname2]) {
NSLog(#"Failed to compile vertex shader2");
return NO;
}
fragShaderPathname2 = [[NSBundle mainBundle] pathForResource:#"Shader2" ofType:#"fsh"];
if (![self compileShader:&fragShader2 type:GL_FRAGMENT_SHADER file:fragShaderPathname2]) {
NSLog(#"Failed to compile fragment shader2");
return NO;
}
glAttachShader(_program2, vertShader2);
glAttachShader(_program2, fragShader2);
glBindAttribLocation(_program2, ATTRIB_VERTEX2, "position2");
if (![self linkProgram:_program2]) {
NSLog(#"Failed to link program: %d", _program2);
if (vertShader2) {
glDeleteShader(vertShader2);
vertShader2 = 0;
}
if (fragShader2) {
glDeleteShader(fragShader2);
fragShader2 = 0;
}
if (_program2) {
glDeleteProgram(_program2);
_program2 = 0;
}
return NO;
}
uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX2] = glGetUniformLocation(_program2, "modelViewProjectionMatrix2");
if (vertShader2) {
glDetachShader(_program2, vertShader2);
glDeleteShader(vertShader2);
}
if (fragShader2) {
glDetachShader(_program2, fragShader2);
glDeleteShader(fragShader2);
}
return YES;
}
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
GLint status;
const GLchar *source;
source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
if (!source) {
NSLog(#"Failed to load vertex shader");
return NO;
}
*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;
}
My vert and fragment shaders are simple:
// vert shader1:
attribute vec4 position;
uniform mat4 modelViewProjectionMatrix;
void main()
{
gl_Position = modelViewProjectionMatrix * position;
}
// vert shader2:
attribute vec4 position2;
uniform mat4 modelViewProjectionMatrix2;
void main()
{
gl_Position = modelViewProjectionMatrix2 * position2;
}
// frag shader(s):
void main()
{
gl_FragColor = vec4(0.12,0.32,0.54,1.0);
}
The most important thing to remember with OpenGL ES, is that you're using a procedural language within an OOP language.
You can only bind one vertex array to the vertex buffer at a time.
Binding two vertex arrays to the VBO, one after the other, and then applying transformations, will only transform the last vertex array attached to the VBO.
In your main loop, you have to iterate through your list of your vertex arrays. For each vertex array, bind it to the VBO, and then carry out any transformations.
The main problem was where I bound the 'position' attribute location for the second vertex shader. I had been using a 'separate' ATTRIB_VERTEX2 in my enum. Once I bound the 'position' attribute location to ATTRIB_VERTEX I was able to see the second VBO with the other shader program applied... Here is the code tidied up for anyone with the same problem/question:
// Uniform index.
enum
{
UNIFORM_MODELVIEWPROJECTION_MATRIX,
UNIFORM_MODELVIEWPROJECTION_MATRIX2,
NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];
// Attribute index.
enum
{
ATTRIB_VERTEX,
NUM_ATTRIBUTES
};
GLfloat square1Data[18] =
{
// Data layout for each line below is:
// positionX, positionY, positionZ
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f
};
GLfloat square2Data[18] =
{
// Data layout for each line below is:
// positionX, positionY, positionZ
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f
};
- (void)setupGL
{
[EAGLContext setCurrentContext:self.context];
[self loadShaders];
//---- First Vertex Array Object --------
glGenVertexArraysOES(1, &_vertexArray1);
glGenBuffers(1, &_vertexBuffer1);
glBindVertexArrayOES(_vertexArray1);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer1);
glBufferData(GL_ARRAY_BUFFER, sizeof(square1Data), square1Data, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 12, BUFFER_OFFSET(0));
// glEnableVertexAttribArray(GLKVertexAttribNormal);
// glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
//----- Second Vertex Array Object ----------
glGenBuffers(1, &_vertexBuffer2);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2);
glBufferData(GL_ARRAY_BUFFER, sizeof(square2Data), square2Data, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 12, BUFFER_OFFSET(0));
// glEnableVertexAttribArray(GLKVertexAttribNormal);
// glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArrayOES(0);
}
- (void)update
{
_rotation += self.timeSinceLastUpdate * 0.2f;
float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(-1.0f, 1.0f, -1.0f / aspect, 1.0f / aspect, -10.0f, 10.0f);
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.5f, 0.0f, 0.0f);
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, GLKMatrix4MakeZRotation(0.0 - _rotation));
_modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
GLKMatrix4 modelViewMatrix2 = GLKMatrix4MakeTranslation(-0.5f, 0.0f, 0.0f);
modelViewMatrix2 = GLKMatrix4Multiply(modelViewMatrix2, GLKMatrix4MakeZRotation(_rotation));
_modelViewProjectionMatrix2 = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix2);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArrayOES(_vertexArray1);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer1);
glUseProgram(_program);
glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m);
glDrawArrays(GL_TRIANGLES, 0, 6);
///////// second VBO and shader program:
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2);
glUseProgram(_program2);
glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX2], 1, 0, _modelViewProjectionMatrix2.m);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
- (BOOL)loadShaders
{
GLuint vertShader, fragShader, vertShader2, fragShader2;
NSString *vertShaderPathname, *fragShaderPathname, *vertShaderPathname2, *fragShaderPathname2;
// Create shader program.
_program = glCreateProgram();
_program2 = glCreateProgram();
// Create and compile vertex shader.
vertShaderPathname = [[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
NSLog(#"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
fragShaderPathname = [[NSBundle mainBundle] pathForResource:#"Shader" ofType:#"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
NSLog(#"Failed to compile fragment shader");
return NO;
}
// Create and compile vertex shader.
vertShaderPathname2 = [[NSBundle mainBundle] pathForResource:#"Shader2" ofType:#"vsh"];
if (![self compileShader:&vertShader2 type:GL_VERTEX_SHADER file:vertShaderPathname2]) {
NSLog(#"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
fragShaderPathname2 = [[NSBundle mainBundle] pathForResource:#"Shader2" ofType:#"fsh"];
if (![self compileShader:&fragShader2 type:GL_FRAGMENT_SHADER file:fragShaderPathname2]) {
NSLog(#"Failed to compile fragment shader");
return NO;
}
// Attach vertex shader to program.
glAttachShader(_program, vertShader);
glAttachShader(_program2, vertShader2);
// Attach fragment shader to program.
glAttachShader(_program, fragShader);
glAttachShader(_program2, fragShader2);
// Bind attribute locations.
// This needs to be done prior to linking.
glBindAttribLocation(_program, ATTRIB_VERTEX, "position");
glBindAttribLocation(_program2, ATTRIB_VERTEX, "position");
// Link 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;
}
if (![self linkProgram:_program2]) {
NSLog(#"Failed to link program: %d", _program2);
if (vertShader2) {
glDeleteShader(vertShader2);
vertShader2 = 0;
}
if (fragShader2) {
glDeleteShader(fragShader2);
fragShader2 = 0;
}
if (_program2) {
glDeleteProgram(_program2);
_program2 = 0;
}
return NO;
}
// Get uniform locations.
uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX] = glGetUniformLocation(_program, "modelViewProjectionMatrix");
uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX2] = glGetUniformLocation(_program2, "modelViewProjectionMatrix2");
// Release vertex and fragment shaders.
if (vertShader) {
glDetachShader(_program, vertShader);
glDeleteShader(vertShader);
}
if (fragShader) {
glDetachShader(_program, fragShader);
glDeleteShader(fragShader);
}
if (vertShader2) {
glDetachShader(_program2, vertShader2);
glDeleteShader(vertShader2);
}
if (fragShader2) {
glDetachShader(_program2, fragShader2);
glDeleteShader(fragShader2);
}
return YES;
}