Tuesday, December 18, 2012

Demo 1 - Basic Drawing (iOS)

This is the iOS version of the android post here. Please note that I try to make these standalone and so a lot of the text will be duplicated. There are some very important differences between android and iOS so I would definitely recommend reading through each if you are interested in both platforms.

Now that we've gotten past the preliminary introduction and hopefully you have downloaded the specifications we can get started on our first demo: basic drawing. For this demo we will be drawing three red squares onto the screen utilizing three different drawing modes. As you will come to realize there are many different ways to achieve the same end result in OpenGL ES 2.0. There are certainly good reasons for using one technique vs another technique otherwise they wouldn't be included in the specification. I will be working with the iOS platform using Objective-C for this demo and you can find the android version using Java following the link above. If there is a high enough demand I might be convinced to add a third utilizing C++ but suffice to say that they will all share the same basic principles.

For this first demo I will be reviewing some of the base code that goes into the basic setup of OpenGL ES 2.0 and the helper classes I have created as well as how I structure my code and why I separate files and other nuances. The hope is that I will review these once and in subsequent demos I will only need to write about the OpenGL ES 2.0 code. One other thing that I have failed to mention before is that these demos will be geared towards the experienced mobile platform developer, either android or iOS. They will assume a basic understanding of programming principles, both object oriented as well as some familiarity with graphic concepts. There are plenty of resource on the web to answer any questions you may have and if you have any specific questions feel free to utilize the comments section where I can try to help out or point to a particular resource for a subject. All demos will be posted on my github account and I will provide a link to the specific demo at the end of the blog post.

The first file we will review is AppDelegate.m, specifically the application:didFinishLaunchingWithOptions: function.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    mWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    
    DemoViewController* glvc = [[DemoViewController alloc] init];
    mWindow.rootViewController = glvc;
    [mWindow makeKeyAndVisible];
    [glvc release];
    return YES;
}

The first line we initialize our window to the size of the screen. This is fairly standard in most iOS apps. Next we instantiate an instance of the DemoViewController class. Don't worry about the details of this class right now as we will review in detail later. What I will say is that DemoViewController inherits from the GLKViewController class which makes things very easy when combined with the GLKView class to display OpenGL ES 2.0 output. If you have used OpenGL ES 2.0 prior to iOS 5.0 it was much more work getting output to the screen with all the manual code to setup OpenGL ES 2.0. Next we set our window's rootViewController to our DemoViewController. Then we make the window visible and release the DemoViewController as it will be retained by the window and we want to prevent memory leaks. All of this is fairly standard for this particular function except for possibly the ViewController class used and some variable names.

The next file we will review is GraphicsUtils.m. This class provides helper functions to make life easier when working with OpenGL ES 2.0. We will review the functions to clarify what they do.

+ (NSString*) readShaderFile:(NSString*) filename {
    NSBundle* bundle = [NSBundle mainBundle];
    NSString* resourcePath = [bundle resourcePath];
    NSString* fullpath = [resourcePath stringByAppendingPathComponent:filename];
    NSString* contents = [NSString stringWithContentsOfFile:fullpath encoding:NSUTF8StringEncoding error:nil];
    return contents;
}

The readShaderFile: function is used to read a text file from the apps resources and return the NSString content of the file. This function is needed to read the fragment shader and vertex shader which are saved as text files. The fragment shader and vertex shader are stored in a text file and not embedded within a class to allow copying and pasting between different projects as well as different project types (android, iOS, etc). You will find various resource online that embed the shaders within a class in an NSString variable but I feel that storing in a text file has provided the best flexibility. In addition, writing a function to read from a text file is fairly trivial in most environments. There is no right or wrong way as both get the job done.

+ (unsigned) createProgramVertexSource:(NSString*) vSource fragmentSource:(NSString*) fSource {
    unsigned programId = glCreateProgram();
    NSAssert1(programId != 0, @"Failed creating program, %@", @"GraphicsUtils:createProgramVertexSource");
    unsigned vertexShader = [self loadShaderForType:GL_VERTEX_SHADER source:vSource];
    glAttachShader(programId, vertexShader);
    unsigned fragmentShader =[self loadShaderForType:GL_FRAGMENT_SHADER source:fSource];
    glAttachShader(programId, fragmentShader);
    glLinkProgram(programId);
    int linkStatus[1];
    glGetProgramiv(programId, GL_LINK_STATUS, linkStatus);
    if (linkStatus[0] != GL_TRUE) {
        NSLog(@"Error");
        glDeleteProgram(programId);
        programId = 0;
    }
    return programId;
}

The createProgramVertexSource:fragmentSource: function creates the program that is used to render. Like most OpenGL functionality, the shader program is first created by generating a program identifier, an unsigned value in Objective-C. Then, the vertex and fragment shaders are loaded through the loadShaderForType:source: function which we will review next. The shaders are then attached to the shader program. Shader programs are at the core of OpenGL ES 2.0. They provide the rendering code that replaces the fixed functionality of OpenGL ES 1.0/1.1. We will review the vertex and fragment shader source later on. Finally the shader program is linked and the status is reviewed to ensure that the program did not generate any errors. This is a tricky situation as you will generally not know if a shader is wrong until you get to this point. Like all functions within GraphicUtils, the createProgram function is static. It will return the program identifier that was generated if everything went well.

A side note: There are IDE's available for shader development but I have not worked with any so I cannot make any recommendations or provide any guidance. There should be many resource online though.

+ (unsigned) loadShaderForType:(unsigned) shaderType source:(NSString*) source {
    uint shader = glCreateShader(shaderType);
    if (shader == 0) {
        NSLog(@"Big problem!");
    }
    const char* str = [source UTF8String];
    glShaderSource(shader, 1, &str, NULL);
    glCompileShader(shader);
    return shader;
}

Here we have the loadShaderForType:source: function that was used in the createProgramVertexSource:fragmentSource: function. This function creates the shaders that are attached to the shader program. Since there are two shaders, the vertex shader and fragment shader, the function is passed the shader type as well as the source code for the shader. The function returns the shader identifier.

+ (void) activateProgram:(unsigned) programId {
    mCurrentProgram = programId;
    glUseProgram(programId);
}

+ (unsigned) currentProgramId {
    return mCurrentProgram;
}

The function activateProgram: is pretty straight forward. It assigns the current program (mCurrentProgram) the program identifier that is passed and uses the program with the glUseProgram function. Likewise, the currentProgramId function returns the current program identifier stored in the static variable mCurrentProgram.

That wraps up the review of the GraphicsUtils class. It's important to note that this class is designed to make life easier when working with OpenGL ES 2.0 and everything contained in this class can be done without the class but in my opinion it will be more work. My goal with the GraphicsUtils class is to reduce the amount of code that we will need to create for each demo and to make an easy transition for you, the reader, to test out functionality on your own. I have not gone into too much depth for the particular OpenGL functions for a few different reasons. The first is that they are pretty self explanatory in my opinion and also because I mainly just use the helper class since it takes care of a lot of things for me. You can definitely read through the specification if you want more in depth information on any of the OpenGL ES 2.0 functions we have reviewed.

Now that we've reviewed the AppDelegate and GraphicsUtils classes we can move on to the core of our demo, the DemoViewController.m file.

- (id) init {
    self = [super init];
    if (self) {
        [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
        EAGLContext* context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        [EAGLContext setCurrentContext:context];
        const CGRect frame = [[UIScreen mainScreen] bounds];
        mWidth = frame.size.width;
        mHeight = frame.size.height;
        GLKView* glView = [[GLKView alloc] initWithFrame:frame context:context];
        glView.delegate = self;
        self.view = glView;
        [context release];
        [glView release];
        [self initGL];
        self.preferredFramesPerSecond = 60;
    }
    return self;
}


The constructor aka init function for the DemoViewController class starts with the standard initialization. Once we have verified that self has been created we remove the status bar so we can utilize the maximum screen space. Next, we initalize the EAGLContext and set the current context to the created EAGLContext. Next we save the values of the screen width and height so they can be used later in the app. An instance of GLKView is then intialized with the screen size and EAGLContext we created earlier. We set the view's delegate to our DemoViewController class which requires the function glkView:drawInRect to be defined. After releasing the EAGLContext and GLKView to prevent memory leaks we call the function initGL. Even though the GLKViewController/GLKView make it much easier to work with OpenGL ES 2.0 there is no initialization function which in my opinion would have been a great complement to the glkView:drawInRect function and made the process more robust. Therefore, I have defined a function that will handle the OpenGL ES 2.0 initialization that will allow us to separate it from the GLKViewController initialization. We will review this function next. Finally we set the preferredFramePerSecond to 60. This can be set to whatever your preferred Frame Per Second should be but in my opinion the higher the better but you will cap out at the screen's refresh rate.

-(void) initGL {
    NSString* vSource;
    NSString* fSource;
    vSource = [GraphicsUtils readShaderFile:@"basic.vsh"];
    fSource = [GraphicsUtils readShaderFile:@"basic.fsh"];
    const uint progId = [GraphicsUtils createProgramVertexSource:vSource fragmentSource:fSource];
    [GraphicsUtils activateProgram:progId];
    //set clear color
    glClearColor(.25f, .25f, .25f, 1);
    //read attribute locations and enable
    maColor = glGetAttribLocation(progId, "aColor");
    glEnableVertexAttribArray(maColor);
    maVertices = glGetAttribLocation(progId, "aVertices");
    glEnableVertexAttribArray(maVertices);
    
    glViewport(0, 0, mWidth, mHeight);
    
    //set matrices and pass to shader
    GLKMatrix4 projMatrix = GLKMatrix4MakeOrtho(0, mWidth, 0, mHeight, -1, 1);
    uint uProjectionMatrix = glGetUniformLocation(progId, "uProjectionMatrix");
    glUniformMatrix4fv(uProjectionMatrix, 1, false, &projMatrix.m[0]);
}

The initGL function is a function that I created for the specific use of initializing our OpenGL ES 2.0 code. I feel that this function along with the glkView:drawInRect would be the necessary functions that should be implemented per the protocol GLKViewDelegate but alas this is a function that we need to define ourselves. Within this function is where I would recommend placing the OpenGL ES 2.0 setup code. Here we read in our vertex shader and fragment shader source files, create the shader program and use the shader program with the functions we reviewed in the GraphicsUtils class.

Following the shader program initialization we will get into a lot of OpenGL ES 2.0 functions. These functions are documented in depth in the specifications which you can download following the links in this blog post. I will give brief overviews of the functions, however, and hopefully the brief descriptions will clarify the function usage. The glClearColor function sets the color that the screen will be cleared with. I will note this when we clear the screen. The glGetAttribLocation gets an identifier that represents the attribute specified, aColor in the first call and aVertices in the second call. The first parameter of glGetAttribLocation is an identifier for the shader program that we want to get the attribute identifier from. Note that an application can have multiple shader programs that may have the same attribute names in each individual shader program so it's necessary to provide the specific program identifier. To further clarify how the functions above relate to the shader files I will provide a snippet from the vertex shader file basic.vsh.

attribute vec4 aVertices;
attribute vec4 aColor;

Here we have the attributes aVertices and aColor defined in basic.vsh, the vertex shader. In order to pass data to the shader program we use the attribute identifiers as parameters for other OpenGL ES 2.0 functions. In the case of multiple vertices data we use vertex attribute arrays that need to be enabled prior to use with the glEnableVertexAttribArray function.  The function takes one parameter, the attribute identifier that was returned from the previous call to glGetAttribLocation. We store the values from glGetAttribLocation in member variables so we can use them in other functions without needing to get the attribute identifiers again. This is a simple performance benefit which should try to be adhered to when working with OpenGL ES 2.0. The less calls to OpenGL ES 2.0 functions the better.

To set the viewport we call the function glViewport passing  0 for the first 2 parameters and the width and height for the next parameters. Basically we are defining the viewport to be the same dimensions as the screen. I have not personally used any other values but there might be resource online that show how to use other values.

Next we set the projection matrix which determines the drawing visibility. Since this demo only works with 2 dimensional drawing we will use the GLKMatrix4MakeOrtho function. This is the easiest way to handle 2 dimensional drawing as you can provide a projection area that matches the screen dimension. These are provided with the 1st, 2nd, 3rd and 4th parameters, respectively. The other parameters can be ignored for now and accepted as just working.

Similar to retrieving attribute identifiers, we need to retrieve uniform identifiers to pass data that will be used within the vertex or fragment shaders. These identifiers are retrieved with the function glGetUniformLocation. The program identifier is passed as the first parameter and the name of the uniform is passed as the second parameter. I have provided a snippet from the vertex shader that shows the uniforms.

uniform mat4 uProjectionMatrix;

I will briefly review what the difference between an attribute and a uniform is and what I understand the intention to be. An attribute is a per vertex piece of data. This can be the actual vertex location, the color of the vertex, the texture coordinate used for the vertex, etc. I will review this more when we get to the next function, glkView:drawInRect,  as that is where we pass the vertex data. A uniform is a piece of data that will be used throughout the draw call and is not on a per vertex basis. The draw calls are not specified in the initGL function but the uniform data will reside in OpenGL ES 2.0 memory (which will I will refer to as  GPU memory from here on out) once it is set. The projection matrix does not need to be updated once we set them as they will not change during the app (we will be using a static "camera", the "camera" being defined by the projection matrix). Hence we set the projection matrix in the initGL function. To get uniform data to the GPU we call the function glUniformMatrix4fv. Depending on what type of uniform data is passed to the GPU the function call may be different as well as the parameter list. Functions for passing uniform data to the GPU will typically be of the form glUniformxxx, where xxx is the data type/value type. In the case of glUniformMatrix4fv, Matrix4 is the data type (mat4 in the shader) and fv is the value type, float vector to be specific.

Hopefully you haven't been overwhelmed with the amount of code we've had to write up until now just to setup some very basic drawing of OpenGL ES 2.0 primatives. We are almost finished with the code with only 1 function left to review. This is the glkView:drawInRect where we actually do the drawing. This is the code that defines what we draw each frame. The glkView:drawInRect function is the only function that is required to be implemented for the GLKViewDelegate protocol.

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
     //clear screen
     glClear(GL_COLOR_BUFFER_BIT);
     
     //set color buffer and pass to shader
     float colors[] = {
     1.0f, 0.0f, 0.0f, 0.0f
     , 1.0f, 0.0f, 0.0f, 0.0f
     , 1.0f, 0.0f, 0.0f, 0.0f
     , 1.0f, 0.0f, 0.0f, 0.0f
     , 1.0f, 0.0f, 0.0f, 0.0f
     , 1.0f, 0.0f, 0.0f, 0.0f
     };
     glVertexAttribPointer(maColor, 4, GL_FLOAT, false, 0, &colors[0]);
     
     //to move quad around screen for drawing modes
     const float offset = 100;
     
     //measure out vertices and set for quad
     const float SIZE = 50;
     const float HALF_WIDTH = mWidth / 2.0f;
     const float HALF_HEIGHT = mHeight / 2.0f;
     
     const float leftX = HALF_WIDTH - SIZE;
     const float rightX = HALF_WIDTH + SIZE;
     const float topY = HALF_HEIGHT + SIZE;
     const float bottomY = HALF_HEIGHT - SIZE;
     
     //triangle strip
     const float verticesTriangleStrip[] = {
     leftX - offset, bottomY + offset
     , leftX - offset, topY + offset
     , rightX - offset, bottomY + offset
     , rightX - offset, topY + offset
     };
     glVertexAttribPointer(maVertices, 2, GL_FLOAT, false, 0, &verticesTriangleStrip[0]);
     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
     
     //triangle fan
     const float verticesTriangleFan[] = {
     leftX + offset, bottomY + offset
     , leftX + offset, topY + offset
     , rightX + offset, topY + offset
     , rightX + offset, bottomY + offset
     };
     glVertexAttribPointer(maVertices, 2, GL_FLOAT, false, 0, &verticesTriangleFan[0]);
     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
     
     //triangles
     const float verticesTriangle[] = {
     leftX, bottomY - offset
     , rightX, bottomY - offset
     , leftX, topY - offset
     , rightX, bottomY - offset
     , leftX, topY - offset
     , rightX, topY - offset
     };
     glVertexAttribPointer(maVertices, 2, GL_FLOAT, false, 0, &verticesTriangle[0]);
     glDrawArrays(GL_TRIANGLES, 0, 6);
}


Wow, that's a lot of code. I won't review it line by line as certain pieces are very similar in nature and once we define what the functionality is the same can be applied for all the similar pieces. The glClear function takes a bitfield of which buffers to clear. From the specification, the values that can be passed are COLOR_BUFFER_BIT, DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT. Right now we will only focus on COLOR_BUFFER_BIT which will be the screen color. The value that the screen will be cleared to is defined with the glClearColor function which we called earlier in the initGL function. Next we define the colors that we will use at each vertex and place the data into an array of float values. Since each drawing mode functions differently for the vertices that are passed to the GPU we will need to provide a different set of data for each drawing mode (GL_TRIANGLES, GL_TRIANGLE_STRIP and GL_TRIANGLE_FAN). We have define the maximum amount (6) of vertex data for the color even though two of the drawing modes won't use all the color data (only 4 vertices worth).

The steps for passing vertex attribute array data breakdown to this: Define the vertex data in an array (float array for this demo). Pass the data to the GPU with the function glVertexAttribPointer, specifying the attribute identifier, the number of per vertex data values, the value type, whether to normalize (disregard for now), the stride of the values (disregard for now) and a pointer to the data. Those are how the parameters breakdown for glVertexAttribPointer. I won't go into too much detail for this first demo since there is already a lot of information to process and I would like to progress slowly. I will go into more detail as I change the types in future demos to show how to reduce overhead but I don't want to make this demo more complicated than it already is.

The glDrawArrays function is where the actual drawing takes place. I have provided an image from the specification that outlines how the drawing takes place depending on the mode that is specified.


The GL_TRIANGLE_STRIP mode draws triangles starting with the first 3 vertices, then uses the previous 2 vertices and the next vertices for each subsequent triangle. This means that there will be 2 triangles drawn with the call: the first triangle will use vertices 0, 1, 2 (1, 2 and 3 in the image) and the next triangle will be drawn with vertices 1, 2 and 3 (2, 3 and 4 in image). The vertex count is 4, the third parameter of the function call. This corresponds to vertex 0, which is the second parameter, through vertex 3.

The GL_TRIANGLE_FAN mode draws triangles around vertex 0 (1 in image). This means there will be 2 triangles drawn. The first triangle is drawn with vertices 0, 1 and 2 (1, 2 and 3 in image) while the second will be drawn with vertices 0, 2 and 3 (1, 3 and 4 in image). The vertex count is 4 which corresponds to vertices 0 to 3.

The GL_TRIANGLES mode draws triangles grouping by sets of 3 vertices. This means there will be 2 triangles drawn. The first triangle will use vertices 0, 1 and 2 (1, 2 and 3 in image). The second triangle wil be drawn with vertices 3, 4 and 5 (4, 5 and 6 in the image). The vertex count is 6 which corresponds to vertices 0 to 5.

The glDrawArrays, when passed the previous values GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN and GL_TRIANGLES will also fill the triangle with the vertex colors as specified previously. We are currently passing the same color to each vertex which is why there is a single color filled in the squares we draw. We will changes these values in the future to demonstrate how different vertex colors will affect what is drawn.

Now that we have reviewed all the Objective-C code that is associated with the demo we will review the vertex and fragment shader code. The code for each is only 1 function but we will also review the data that is passed to each shader.

The first shader we will review is the vertex shader. The vertex shader is used for handling vertex data for our OpenGL ES 2.0 primatives.

uniform mat4 uProjectionMatrix;

attribute vec4 aVertices;
attribute vec4 aColor;

varying vec4 vColor;

void main() {
    gl_Position = uProjectionMatrix * aVertices;
    vColor = aColor;
}

The first 3 variable we define are the uProjectionMatrix uniform, aVertices attribute and aColor attribute that have been reviewed already. The next variable is the vColor varying. A varying is a value that will change over the rendered primative when passed from the vertex shader to fragment shader. Since the vertex shader deals with vertex data a varying variable is used so a shader program can interpolate the data between the vertices. This is because the fragment shader deals with pixel data. Don't worry if this doesn't immediately sink in. As you progress and work more with shaders you will hopefully develop a better understanding of the different types of variables. The main function is only 2 lines. The first is to set the built-in vertex shader variable gl_Position. This is the position of the vertex. We will multiply the projection matrix by the vertex to get the final position of the vertex. The next line is to set the varying vColor to the color we passed in to the attribute aColor. Attributes can only be passed to the vertex shader so we must pass the data to the fragment shader via varying variables.

Onto the final piece of code we have the fragment shader.

precision mediump float;

varying vec4 vColor;

void main() {
    gl_FragColor = vColor;
}

The first line declare the precision for float values. Taken directly from the specification:

The fragment language has no default precision qualifier for floating point types. Hence for float, floating point vector and matrix variable declarations, either the declaration must include a precision qualifier or the default float precision must have been previously declared.

Therefore we declare the precision for float values at the beginning of the fragment shader. You can read more about precision in the specification. Next we declare the varying that was passed from the vertex shader. Yes, they need to be declared in both shaders. Finally, our main function has a single line which is to assign the color value to the fragment shader built-in variable gl_FragColor. This is the color of the pixel that is rendered.

It can be overwhelming when reviewing the amount of code needed for such simple drawing in OpenGL ES 2.0 when compared to some alternative built-in graphic libraries or other graphic API's. I have glossed over certain sections, stating that you will not need to fully understand what is occurring, as it can take a while for some of this information to sink in. As I provide more demos I will go in more depth on the functionality but for the very basics there will be some code that should be acknowledge as to provide the necessary functionality without your complete understanding. I apologize for this but I feel baby steps are a preferred method than trying to drink from a firehose.

I hope this post has been informative in how to get started with basic drawing with OpenGL ES 2.0 on the iOS mobile platform. The project can be downloaded from github. Please let me know if you have any issues or questions.

Wednesday, December 12, 2012

Demo 1 - Basic Drawing (android)

Now that we've gotten past the preliminary introduction and hopefully you have downloaded the specifications we can get started on our first demo: basic drawing. For this demo we will be drawing three red squares onto the screen utilizing three different drawing modes. As you will come to realize there are many different ways to achieve the same end result in OpenGL ES 2.0. There are certainly good reasons for using one technique vs another technique otherwise they wouldn't be included in the specification. I will be working in android using Java for this demo but I will also be adding another post that is centered around iOS working with Objective-C. If there is a high enough demand I might be convinced to add a third utilizing C++ but suffice to say that they will all share the same basic principles.

For this first demo I will be reviewing some of the base code that goes into the basic setup of OpenGL ES 2.0 and the helper classes I have created as well as how I structure my code and why I separate files and other nuances. The hope is that I will review these once and in subsequent demos I will only need to write about the OpenGL ES 2.0 code. One other thing that I have failed to mention before is that these demos will be geared towards the experienced mobile platform developer, either android or iOS. They will assume a basic understanding of programming principles, both object oriented as well as some familiarity with graphic concepts. There are plenty of resource on the web to answer any questions you may have and if you have any specific questions feel free to utilize the comments section where I can try to help out or point to a particular resource for a subject. All demos will be posted on my github account and I will provide a link to the specific demo at the end of the blog post.

The first file we will review is MainActivity.java, specifically the onCreate function.

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GraphicsUtils.setContext(this);
        GLSurfaceView view = new GLSurfaceView(this);
        OGLES2Renderer renderer = new OGLES2Renderer(view);
        view.setRenderer(renderer);
        setContentView(view);
    }

The first line is the standard call to the super class and the last line for setting the content view should be familiar. We will review everything in between those two lines that probably looks different than your standard android Activity. The first call to the static function GraphicsUtils.setContext is for storing the Context, which the Activity class is derived from. The Context is used to access resources such as text files, image files, etc. When we review the GraphicsUtils class this will be apparent. The next line is creating an instance of the GLSurfaceView class. This allows OpenGL ES rendering by being set by setContentView(view) at the end of the function. The next line instantiates an OGLES2Renderer class which is where we will write most of our OpenGL ES 2.0 code. I won't go into details now except to say that the class implements the GLSurfaceView.Renderer interface. The interface defines functions for handling different stages of the OpenGL ES life-cycle. The next line sets the renderer for our GLSurfaceView to the instantiated OGLES2Renderer. The setRenderer function of GLSurfaceView takes a class that implements the GLSurfaceView.Renderer interface. The code within MainActivity.java is only a few lines but nevertheless essential to getting OpenGL ES up and running on android.

The next file we will review is GraphicsUtils.java. This class provides helper functions to make life easier when working with OpenGL ES 2.0. We will review the functions to clarify what they do.

    public static void setContext(Context context) {
        mContext = context;
    }

Here is the function that was called from MainActivity.java. It's static so the variable mContext needs to be static. All it does is store the Context so we can use it at a later time.

    public static String readShaderFile(String filename) {
        StringBuffer resultBuffer = new StringBuffer();
        try {
            final int BUFFER_SIZE = 1024;
            char buffer[] = new char[BUFFER_SIZE];
            int charsRead;
            InputStream stream = mContext.getAssets().open(filename);
            InputStreamReader reader = new InputStreamReader(stream);
            resultBuffer = new StringBuffer();
            while ((charsRead = reader.read(buffer, 0, BUFFER_SIZE)) != -1) {
                resultBuffer.append(buffer, 0, charsRead);
            }
            reader.close();
            stream.close();
        } catch (Exception e) {
            Log.v("info", "very bad");
        }
        return resultBuffer.toString();
    }

The readShaderFile function is used to read a text file from the assets folder and return the String content of the file. It uses the Context that we set previously in order to access the files in the assets folder. This function is needed to read the fragment shader and vertex shader which are saved as text files. The fragment shader and vertex shader are stored in a text file and not embedded within a class to allow copying and pasting between different projects as well as different project types (android, iOS, etc). You will find various resource online that embed the shaders within a class in a String variable but I feel that storing in a text file has provided the best flexibility. In addition, writing a function to read from a text file is fairly trivial in most environments. There is no right or wrong way as both get the job done.

    public static int createProgram(String vertexSource, String fragmentSource) {
        int program = GLES20.glCreateProgram();
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        GLES20.glAttachShader(program, vertexShader);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        GLES20.glAttachShader(program, fragmentShader);
        GLES20.glLinkProgram(program);
        int[] linkStatus = new int[1];
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
        if (linkStatus[0] != GLES20.GL_TRUE) {
            Log.e("Error", "Could not link program: ");
            Log.e("Error", GLES20.glGetProgramInfoLog(program));
            GLES20.glDeleteProgram(program);
            program = 0;
        }
        return program;
    }

The createProgram function creates the program that is used to render. Like most OpenGL functionality, the shader program is first created by generating a program identifier, an int value in Java. Then, the vertex and fragment shaders are loaded through the loadShader function which we will review next. The shaders are then attached to the shader program. Shader programs are at the core of OpenGL ES 2.0. They provide the rendering code that replaces the fixed functionality of OpenGL ES 1.0/1.1. We will review the vertex and fragment shader source later on. Finally the shader program is linked and the status is reviewed to ensure that the program did not generate any errors. This is a tricky situation as you will generally not know if a shader is wrong until you get to this point. Like all functions within GraphicUtils, the createProgram function is static. It will return the program identifier that was generated if everything went well.

A side note: There are IDE's available for shader development but I have not worked with any so I cannot make any recommendations or provide any guidance. There should be many resource online though.

    private static int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        GLES20.glShaderSource(shader, source);
        checkGlError("glShaderSource");
        GLES20.glCompileShader(shader);
        checkGlError("glCompileShader");
        return shader;
    }

Here we have the loadShader function that was used in the createProgram function. This function creates the shaders that are attached to the shader program. Since there are two shaders, the vertex shader and fragment shader, the loadShader function is passed the shader type as well as the source code for the shader. The function returns the shader identifier.

    public static void checkGlError(String op) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            //String errorMsg = GLES20.glGetProgramInfoLog(mCurrentProgram);
            //Log.e("info", op + ": glError " + error + errorMsg);
            throw new RuntimeException(op + ": glError " + error);
        }
    }

The checkGlError function provides error handling to check whether a particular OpenGL ES 2.0 function has generated an error. Error identification/handling is very important in OpenGL ES 2.0 troubleshooting because it will enable you to understand when and possibly why certain functionality isn't working. A lot of the time a certain OpenGL ES 2.0 function will not cause the application to crash but when you check the error generated after it will identify something went wrong and thus the reason the application isn't functioning as expected.

    public static void activateProgram(int programId) {
        mCurrentProgram = programId;
        GLES20.glUseProgram(programId);
    }
    
    public static int currentProgramId() {
        return mCurrentProgram;
    }

The function activateProgram is pretty straight forward. It assigns the current program (mCurrentProgram) the program identifier that is passed and uses the program with the GLES20.glUseProgram function. Likewise, the currentProgramId function returns the current program identifier stored in the member variable mCurrentProgram.

That wraps up the review of the GraphicsUtils class. It's important to note that this class is designed to make life easier when working with OpenGL ES 2.0 and everything contained in this class can be done without the class but in my opinion it will be more work. My goal with the GraphicUtils class is to reduce the amount of code that we will need to create for each demo and to make an easy transition for you, the reader, to test out functionality on your own. I have not gone into too much depth for the particular OpenGL functions for a few different reasons. The first is that they are pretty self explanatory in my opinion and also because I mainly just use the helper class since it takes care of a lot of things for me. You can definitely read through the specification if you want more in depth information on any of the OpenGL ES 2.0 functions we have reviewed.

Now that we've reviewed the MainActivity and GraphicsUtils java files we can move on to the core of our demo, the OGLES2Renderer.java file.

    public OGLES2Renderer(GLSurfaceView view) {
        super();
        //set OGLES version
        view.setEGLContextClientVersion(2);
    }

The constructor for our renderer takes a GLSurfaceView as a parameter. This is so we can set the OpenGL ES version with the setEGLContextClientVersion. Since we will be working exclusively with OpenGL ES 2.0 we set the version to 2.

    public void onSurfaceCreated(GL10 arg0, EGLConfig arg1) {
        String vSource;
        String fSource;
        vSource = GraphicsUtils.readShaderFile("texture.vsh");
        fSource = GraphicsUtils.readShaderFile("texture.fsh");
        final int progId = GraphicsUtils.createProgram(vSource, fSource);
        GraphicsUtils.activateProgram(progId);
        //set clear color
        GLES20.glClearColor(0, 0, 0, 1);
        //read attribute locations and enable
        maColor = GLES20.glGetAttribLocation(progId, "aColor");
        GLES20.glEnableVertexAttribArray(maColor);
        maVertices = GLES20.glGetAttribLocation(progId, "aVertices");
        GLES20.glEnableVertexAttribArray(maVertices);
    }

The onSurfaceCreated function is one of three functions that you will need to implement for the GLSurfaceView.Renderer interface. It is where I would recommend placing the OpenGL ES 2.0 setup code. Here we read in our vertex shader and fragment shader source files, create the shader program and use the shader program with the functions we reviewed in the GraphicsUtils class.

Following the shader program initialization we will get into a lot of OpenGL ES 2.0 functions. These functions are documented in depth in the specifications which you can download following the links in the previous blog post. I will give brief overviews of the functions, however, and hopefully the brief descriptions will clarify the function usage. The glClearColor function sets the color that the screen will be cleared with. I will note this when we clear the screen. The glGetAttribLocation gets an identifier that represents the attribute specified, aColor in the first call and aVertices in the second call. The first parameter of glGetAttribLocation is an identifier for the shader program that we want to get the attribute identifier from. Note that an application can have multiple shader programs that may have the same attribute names in each individual shader program so it's necessary to provide the specific program identifier. To further clarify how the functions above relate to the shader files I will provide a snippet from the vertex shader file basic.vsh.

attribute vec4 aVertices;
attribute vec4 aColor;

Here we have the attributes aVertices and aColor defined in basic.vsh, the vertex shader. In order to pass data to the shader program we use the attribute identifiers as parameters for other OpenGL ES 2.0 functions. In the case of multiple vertices data we use vertex attribute arrays that need to be enabled prior to use with the glEnableVertexAttribArray function.  The function takes one parameter, the attribute identifier that was returned from the previous call to glGetAttribLocation. We store the values from glGetAttribLocation in member variables so we can use them in other functions without needing to get the attribute identifiers again. This is a simple performance benefit which should try to be adhered to when working with OpenGL ES 2.0. The less calls to OpenGL ES 2.0 functions the better.

    public void onSurfaceChanged(GL10 arg0, int width, int height) {
        mWidth = width;
        mHeight = height;
        
        GLES20.glViewport(0, 0, width, height);
        
        //set matrices and pass to shader
        float projMatrix[] = new float[16];
        Matrix.orthoM(projMatrix, 0, 0, width, 0, height, -1, 1);
        final int progId = GraphicsUtils.currentProgramId();
        int uProjectionMatrix = GLES20.glGetUniformLocation(progId, "uProjectionMatrix");
        GLES20.glUniformMatrix4fv(uProjectionMatrix, 1, false, projMatrix, 0);
    }

The onSurfaceChanged function is another function that is required to be implemented for the GLSurfaceView.Renderer interface. It is called when the screen dimensions change, either upon initial setup or when the screen rotates. First we save the values of width and height into member variables so we can use them at a later time. Next we set the viewport with glViewport passing  0 for the first 2 parameters and the width and height for the next parameters. Basically we are defining the viewport to be the same dimensions that are passed to the onSurfaceChanged function. I have not personally used any other values but there might be resource online that show how to use other values.

Next we set the projection matrix which determines the visibility of the drawing that we will use. Since this demo only works with 2 dimensional drawing we will use the OrthoM function. This is the easiest way to handle 2 dimensional drawing as you can provide a projection area that matches the screen dimension. These are provided with the 3rd, 4th, 5th and 6th parameters, respectively. The other parameters can be ignored for now and accepted as just working.

Similar to retrieving attribute identifiers, we need to retrieve uniform identifiers to pass data that will be used within the vertex or fragment shaders. These identifiers are retrieved with the function glGetUniformLocation. The program identifier is passed as the first parameter and the name of the uniform is passed as the second parameter. I have provided a snippet from the vertex shader that shows the uniforms.

uniform mat4 uProjectionMatrix;

I will briefly review what the difference between an attribute and a uniform is and what I understand the intention to be. An attribute is a per vertex piece of data. This can be the actual vertex location, the color of the vertex, the texture coordinate used for the vertex, etc. I will review this more when we get to the next function, onDrawFrame,  as that is where we pass the vertex data. A uniform is a piece of data that will be used throughout the draw call and is not on a per vertex basis. The draw calls are not specified in the onSurfaceChanged function but the uniform data will reside in OpenGL ES 2.0 memory (which will I will refer to as  GPU memory from here on out) once it is set. The projection matrix does not need to be updated once we set them as they will not change during the app (we will be using a static "camera", the "camera" being defined by the projection matrix). Hence we set the projection matrix in the onSurfaceChanged function because they are based on the surface width and height which is updated when the onSurfaceChanged function is called. To get uniform data to the GPU we call the function glUniformMatrix4fv. Depending on what type of uniform data is passed to the GPU the function call may be different as well as the parameter list. Functions for passing uniform data to the GPU will typically be of the form glUniformxxx, where xxx is the data type/value type. In the case of glUniformMatrix4fv, Matrix4 is the data type (mat4 in the shader) and fv is the value type, float vector to be specific.

Hopefully you haven't been overwhelmed with the amount of code we've had to write up until now just to setup some very basic drawing of OpenGL ES 2.0 primatives. We are almost finished with the Java code with only 1 function left to review. This is the onDrawFrame where we actually do the drawing. As it's name implies, this is the code that defines what we draw each frame. The onDrawFrame function is the last function that is required to be implemented for the GLSurfaceView.Renderer interface.

    public void onDrawFrame(GL10 arg0) {
        //clear screen
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        //set color buffer and pass to shader
        float colors[] = {
            1.0f, 0.0f, 0.0f, 0.0f
            , 1.0f, 0.0f, 0.0f, 0.0f
            , 1.0f, 0.0f, 0.0f, 0.0f
            , 1.0f, 0.0f, 0.0f, 0.0f
            , 1.0f, 0.0f, 0.0f, 0.0f
            , 1.0f, 0.0f, 0.0f, 0.0f
        };
        FloatBuffer colorBuffer = ByteBuffer.allocateDirect(colors.length
                * FLOAT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
        colorBuffer.put(colors).position(0);
        GLES20.glVertexAttribPointer(maColor, 4, GLES20.GL_FLOAT, false, 0, colorBuffer);

        //to move quad around screen for drawing modes
        final float offset = 150;

        //measure out vertices and set for quad
        final float SIZE = 50;
        final float HALF_WIDTH = mWidth / 2.0f;
        final float HALF_HEIGHT = mHeight / 2.0f;
        
        final float leftX = HALF_WIDTH - SIZE;
        final float rightX = HALF_WIDTH + SIZE;
        final float topY = HALF_HEIGHT + SIZE;
        final float bottomY = HALF_HEIGHT - SIZE;
        
        FloatBuffer verticesBuffer;
        
        //triangle strip
        final float verticesTriangleStrip[] = {
            leftX - offset, bottomY + offset
            , leftX - offset, topY + offset
            , rightX - offset, bottomY + offset
            , rightX - offset, topY + offset
        };
        verticesBuffer = ByteBuffer.allocateDirect(verticesTriangleStrip.length
                * FLOAT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
        verticesBuffer.put(verticesTriangleStrip).position(0);
        GLES20.glVertexAttribPointer(maVertices, 2, GLES20.GL_FLOAT, false, 0, verticesBuffer);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        //triangle fan
        final float verticesTriangleFan[] = {
            leftX + offset, bottomY + offset
            , leftX + offset, topY + offset
            , rightX + offset, topY + offset
            , rightX + offset, bottomY + offset
        };
        verticesBuffer = ByteBuffer.allocateDirect(verticesTriangleFan.length
                * FLOAT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
        verticesBuffer.put(verticesTriangleFan).position(0);
        GLES20.glVertexAttribPointer(maVertices, 2, GLES20.GL_FLOAT, false, 0, verticesBuffer);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);
        
        //triangles
        final float verticesTriangle[] = {
            leftX, bottomY - offset
            , rightX, bottomY - offset
            , leftX, topY - offset
            , rightX, bottomY - offset
            , leftX, topY - offset
            , rightX, topY - offset
        };
        verticesBuffer = ByteBuffer.allocateDirect(verticesTriangle.length
                * FLOAT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
        verticesBuffer.put(verticesTriangle).position(0);
        GLES20.glVertexAttribPointer(maVertices, 2, GLES20.GL_FLOAT, false, 0, verticesBuffer);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
    }

Wow, that's a lot of code. I won't review it line by line as certain pieces are very similar in nature and once we define what the functionality is the same can be applied for all the similar pieces. The glClear function takes a bitfield of which buffers to clear. From the specification, the values that can be passed are COLOR_BUFFER_BIT, DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT. Right now we will only focus on COLOR_BUFFER_BIT which will be the screen color. The value that the screen will be cleared to is defined with the glClearColor function which we called earlier in the onSurfaceCreated function. Next we define the colors that we will use at each vertex and place the data into a FloatBuffer. This is the class that we need to store data in as the call to glVertexAttribPointer takes a Buffer as the final parameter. This can be a different subclass of Buffer, such as a ShortBuffer, but since we are using float data we pass a FloatBuffer as the parameter. We use the same conversion, that is converting from a float array to a FloatBuffer, for each set of vertex data. Since each drawing mode functions differently for the vertices that are passed to the GPU we will need to provide a different set of data for each drawing mode (GL_TRIANGLES, GL_TRIANGLE_STRIP and GL_TRIANGLE_FAN). We have provided the maximum amount (6) of vertex data for the color even though two of the drawing modes won't use all the color data (only 4 vertices worth).

The steps for passing vertex attribute array data breakdown to this: Define the vertex data in an array (float array for this demo). Allocate a ByteBuffer and store the data in a type specific buffer which will be a FloatBuffer in this demo. Store the data into the Buffer with the put function. Pass the data to the GPU with the function glVertexAttribPointer, specifying the attribute identifier, the number of per vertex data values, the value type, whether to normalize (disregard for now), the stride of the values (disregard for now) and the data. Those are how the parameters breakdown for glVertexAttribPointer. I won't go into too much detail for this first demo since there is already a lot of information to process and I would like to progress slowly. I will go into more detail as I change the types in future demos to show how to reduce overhead but I don't want to make this demo more complicated than it already is.

The glDrawArrays function is where the actual drawing takes place. I have provided an image from the specification that outlines how the drawing takes place depending on the mode that is specified.


The GL_TRIANGLE_STRIP mode draws triangles starting with the first 3 vertices, then uses the previous 2 vertices and the next vertices for each subsequent triangle. This means that there will be 2 triangles drawn with the call: the first triangle will use vertices 0, 1, 2 (1, 2 and 3 in the image) and the next triangle will be drawn with vertices 1, 2 and 3 (2, 3 and 4 in image). The vertex count is 4, the third parameter of the function call. This corresponds to vertex 0, which is the second parameter, through vertex 3.

The GL_TRIANGLE_FAN mode draws triangles around vertex 0 (1 in image). This means there will be 2 triangles drawn. The first triangle is drawn with vertices 0, 1 and 2 (1, 2 and 3 in image) while the second will be drawn with vertices 0, 2 and 3 (1, 3 and 4 in image). The vertex count is 4 which corresponds to vertices 0 to 3.

The GL_TRIANGLES mode draws triangles grouping by sets of 3 vertices. This means there will be 2 triangles drawn. The first triangle will use vertices 0, 1 and 2 (1, 2 and 3 in image). The second triangle wil be drawn with vertices 3, 4 and 5 (4, 5 and 6 in the image). The vertex count is 6 which corresponds to vertices 0 to 5.

The glDrawArrays, when passed the previous values GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN and GL_TRIANGLES will also fill the triangle with the vertex colors as specified previously. We are currently passing the same color to each vertex which is why there is a single color filled in the squares we draw. We will changes these values in the future to demonstrate how different vertex colors will affect what is drawn.

Now that we have reviewed all the Java code that is associated with the demo we will review the vertex and fragment shader code. The code for each is only 1 function but we will also review the data that is passed to each shader.

The first shader we will review is the vertex shader. The vertex shader is used for handling vertex data for our OpenGL ES 2.0 primatives.

uniform mat4 uProjectionMatrix;

attribute vec4 aVertices;
attribute vec4 aColor;

varying vec4 vColor;

void main() {
    gl_Position = uProjectionMatrix * aVertices;
    vColor = aColor;
}

The first 3 variable we define are the uProjectionMatrix uniform, aVertices attribute and aColor attribute that have been reviewed already. The next variable is the vColor varying. A varying is a value that will change over the rendered primative when passed from the vertex shader to fragment shader. Since the vertex shader deals with vertex data a varying variable is used so a shader program can interpolate the data between the vertices. This is because the fragment shader deals with pixel data. Don't worry if this doesn't immediately sink in. As you progress and work more with shaders you will hopefully develop a better understanding of the different types of variables. The main function is only 2 lines. The first is to set the built-in vertex shader variable gl_Position. This is the position of the vertex. We will multiply the projection matrix by the vertex to get the final position of the vertex. The next line is to set the varying vColor to the color we passed in to the attribute aColor. Attributes can only be passed to the vertex shader so we must pass the data to the fragment shader via varying variables.

Onto the final piece of code we have the fragment shader.

precision mediump float;

varying vec4 vColor;

void main() {
    gl_FragColor = vColor;
}

The first line declare the precision for float values. Taken directly from the specification:

The fragment language has no default precision qualifier for floating point types. Hence for float, floating point vector and matrix variable declarations, either the declaration must include a precision qualifier or the default float precision must have been previously declared.

Therefore we declare the precision for float values at the beginning of the fragment shader. You can read more about precision in the specification. Next we declare the varying that was passed from the vertex shader. Yes, they need to be declared in both shaders. Finally, our main function has a single line which is to assign the color value to the fragment shader built-in variable gl_FragColor. This is the color of the pixel that is rendered.

It can be overwhelming when reviewing the amount of code needed for such simple drawing in OpenGL ES 2.0 when compared to some alternative built-in graphic libraries or other graphic API's. I have glossed over certain sections, stating that you will not need to fully understand what is occurring, as it can take a while for some of this information to sink in. As I provide more demos I will go in more depth on the functionality but for the very basics there will be some code that should be acknowledge as to provide the necessary functionality without your complete understanding. I apologize for this but I feel baby steps are a preferred method to trying to drink from a firehose.

I hope this post has been informative in how to get started with basic drawing with OpenGL ES 2.0 on the android mobile platform. The project can be downloaded from github. Please let me know if you have any issues or questions.

Monday, December 3, 2012

Essential Downloads

In order to maximize your opportunity for success with OpenGL ES 2.0, I would highly recommend downloading the two specifications: the full OpenGL ES 2.0 specfication along with the OpenGL ES Shading Language specification. These two documents will help immensely when you need a quick reference to review function parameters, function names, view diagrams for functionality and they should answer just about any technical question that may arise. Of course, it won't suffice when you are in need of a person to explain a certain concept as sometimes the specification can be quite vague and light on sample code but it is invaluable nonetheless. In addition, they will come in handy when you are without an internet connection as they are provided in PDF format. As much as I search online when I can, having a PDF document available when you're flying high in the sky can give you that quick reference that you need.