Drawing with OpenGL ES

This section discusses drawing commands and other aspects of drawing, including loading a .png file into a texture, using framebuffer objects, and using the Glview (libglview) library.

Draw with OpenGL ES 1.1

The Momentics IDE for BlackBerry has OpenGL ES template projects for you to use. The Create a basic 2-D app in C tutorial describes how to initialize your OpenGL ES model and update the model per frame using the OpenGL ES 1.1 template.

Draw with OpenGL ES 2.0

This section discusses a sample app using OpenGL ES 2.0, based on the OpenGL ES 2.0 project template. The app shows a multicolored square in the center of the screen.

The app uses two functions to make OpenGL ES calls: initialize() and render(). The initialize() function initializes the app logic. The app calls render() in the main app loop to update the screen buffer.

Initialize the app logic

The initialize() function initializes the OpenGL ES model. There are some static variables that you need to declare: arrays for vertex and color data, a program object, variables for shader language variables, an identity matrix for calculations, ID variables for vertex and color arrays, and the screen context.

Not applicable

Not applicable

static GLfloat vertices[8];
static GLfloat colors[16];

static GLuint program;

static GLuint transformLoc;
static GLuint positionLoc;
static GLuint colorLoc;

static GLfloat matrix[16] = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};

static GLuint vertexID;
static GLuint colorID;

static screen_context_t screen_cxt;

Because this sample app uses the same graphical model as the OpenGL ES 1.1 template project, but uses OpenGL ES 2.0, your vertex and color data is the same (four 2-D vertices and four RGBA colors).

Not applicable

Not applicable

    vertices[0] = -0.25f;
    vertices[1] = -0.25f;

    vertices[2] = 0.25f;
    vertices[3] = -0.25f;

    vertices[4] = -0.25f;
    vertices[5] = 0.25f;

    vertices[6] = 0.25f;
    vertices[7] = 0.25f;

    colors[0] = 1.0f;
    colors[1] = 0.0f;
    colors[2] = 1.0f;
    colors[3] = 1.0f;

    colors[4] = 1.0f;
    colors[5] = 1.0f;
    colors[6] = 0.0f;
    colors[7] = 1.0f;

    colors[8] = 0.0f;
    colors[9] = 1.0f;
    colors[10] = 1.0f;
    colors[11] = 1.0f;

    colors[12] = 0.0f;
    colors[13] = 1.0f;
    colors[14] = 1.0f;
    colors[15] = 1.0f;

Next, you query the current EGL surface to obtain the surface width and height.

Not applicable

Not applicable

    EGLint surface_width, surface_height;

    eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
    eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);

Next, you specify the source of the vertex shader using the OpenGL ES Shading Language. You specify the source as an array of strings in C-style syntax. Vertex shaders run once for each given vertex. For this vertex shader, you override the high-precision floating-point values, which are the default setting, to have a medium-precision floating-point values because you're not doing anything too complex. Next, you declare the global variables that you want to use, the projection and transformation matrices that you want to use across the entire primitive type, and a position and a color vector for each vertex. You also declare a varying color variable that is interpolated across models. You can access this varying color variable from the fragment shader.

In the main function of the vertex shader source, you calculate the final position of the vertex by multiplying the original position by the transformation matrix and projection matrix. Then, you set the interpolated color as the vertex color:

Not applicable

Not applicable

    const char* vSource =				
            "precision mediump float;"	
            "uniform mat4 u_projection;"	
            "uniform mat4 u_transform;"		
            "attribute vec4 a_position;"	
    		"attribute vec4 a_color;"		
            "varying vec4 v_color;"			
            "void main()"
            "{"								
            "    gl_Position = u_projection * u_transform * a_position;"	
            "    v_color = a_color;"										
            "}";

Next, you specify the fragment shader. Fragment shaders can calculate diffuse and specular lighting values for each fragment, or pixel, that is drawn. In the fragment shader source, you declare the varying color variable that you originally declared in the vertex shader. In the main function of the fragment shader, you set the final fragment color as the varying color variable.

Not applicable

Not applicable

    const char* fSource =
            "varying lowp vec4 v_color;"
            "void main()"
            "{"
            "    gl_FragColor = v_color;"
            "}";

For more information about the OpenGL ES Shading Language, see the Khronos specification or the manual pages.

Now that you specified your vertex and fragment shader source code, you need to create the shaders. You start with creating the vertex shader by calling glCreateShader(GL_VERTEX_SHADER). Then, you attach the vertex source code and compile the shader. You also check for compile time errors to ensure that the shader was successfully compiled.

Not applicable

Not applicable

    GLint status;

    /* Compile the vertex shader */
    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    if (!vs) {
        fprintf(stderr, "Failed to create vertex shader: %d\n", glGetError());
        return EXIT_FAILURE;
    } else {
        glShaderSource(vs, 1, &vSource, 0);
        glCompileShader(vs);
        glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
        if (GL_FALSE == status) {
            GLchar log[256];
            glGetShaderInfoLog(vs, 256, NULL, log);

            fprintf(stderr, "Failed to compile vertex shader: %s\n", log);

            glDeleteShader(vs);
        }
    }

You specify the fragment shader next, following the same process as the vertex shader. If you have an error during compile time, you need to delete both shaders.

Not applicable

Not applicable

    /* Compile the fragment shader */
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    if (!fs) {
        fprintf(stderr, "Failed to create fragment shader: %d\n", glGetError());
        return EXIT_FAILURE;
    } else {
        glShaderSource(fs, 1, &fSource, 0);
        glCompileShader(fs);
        glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
        if (GL_FALSE == status) {
            GLchar log[256];
            glGetShaderInfoLog(fs, 256, NULL, log);

            fprintf(stderr, "Failed to compile fragment shader: %s\n", log);

            glDeleteShader(vs);
            glDeleteShader(fs);

            return EXIT_FAILURE;
        }
    }

Now, you create a program object to attach and link your shaders. You create program objects by calling glCreateProgram(). If the program object is successfully created, you attach the vertex and fragment shaders. Then, you link the program object and check for any errors. Finally, you call glUseProgram() to set the program object as part of the current rendering state.

Not applicable

Not applicable

    /* Create and link the program */
    program = glCreateProgram();
    if (program)
    {
        glAttachShader(program, vs);
        glAttachShader(program, fs);
        glLinkProgram(program);

        glGetProgramiv(program, GL_LINK_STATUS, &status);
        if (status == GL_FALSE)    {
            GLchar log[256];
            glGetProgramInfoLog(fs, 256, NULL, log);

            fprintf(stderr, "Failed to link shader program: %s\n", log);

            glDeleteProgram(program);
            program = 0;

            return EXIT_FAILURE;
        }
    } else {
        fprintf(stderr, "Failed to create a shader program\n");

        glDeleteShader(vs);
        glDeleteShader(fs);
        return EXIT_FAILURE;
    }

    glUseProgram(program);

Now you must set up the orthographic projection matrix. You obtain the projection matrix that you defined in the vertex shader. You calculate the horizontal, vertical, near, and far clipping plane values. Based on the matrix transformation described in glOrtho(), you specify the orthographic matrix. Then, you pass that matrix to your projection matrix from the shader language.

Not applicable

Not applicable

    GLuint projectionLoc = glGetUniformLocation(program, "u_projection");
    {
        GLfloat left = 0.0f;
        GLfloat right = (float)(surface_width) / (float)(surface_height);
        GLfloat bottom = 0.0f;
        GLfloat top = 1.0f;
        GLfloat zNear = -1.0f;
        GLfloat zFar = 1.0f;
        GLfloat ortho[16] = {2.0 / (right-left), 0, 0, 0,
                            0, 2.0 / (top-bottom), 0, 0,
                            0, 0, -2.0 / (zFar - zNear), 0,
                            -(right+left)/(right-left), -(top+bottom)/(top-bottom), -(zFar+zNear)/(zFar-zNear), 1};
        glUniformMatrix4fv(projectionLoc, 1, false, ortho);
    }

After you define the orthographic projection matrix, you store the other shader variables that you defined in the vertex shader. You can also delete the shaders now, because the program object is enough for the rest of the initialization.

Not applicable

Not applicable

    transformLoc = glGetUniformLocation(program, "u_transform");
    positionLoc = glGetAttribLocation(program, "a_position");
    colorLoc = glGetAttribLocation(program, "a_color");

    glDeleteShader(fs);
    glDeleteShader(vs);

Next, you generate vertex and color buffers and fill them with the vertex and color data that you specified earlier.

Not applicable

Not applicable

    glGenBuffers(1, &vertexID);
    glBindBuffer(GL_ARRAY_BUFFER, vertexID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glGenBuffers(1, &colorID);
    glBindBuffer(GL_ARRAY_BUFFER, colorID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);

Finally, you perform the same translation that you did in the OpenGL ES 1.1 template project. The value of matrix[12] acts as the x coordinate for the translation vector and the value of matrix[13] acts as the y coordinate of the translation vector.

Not applicable

Not applicable

    matrix[12] = (float)(surface_width) / (float)(surface_height) / 2;
    matrix[13] = 0.5;
    glUniformMatrix4fv(transformLoc, 1, false, matrix);

And that initializes your app! You move on to the per-frame rendering now.

Render on a per-frame basis

To render on a per-frame basis, you write a function that you call from the main app loop called render().

Inside render(), you declare a static float variable for the rotation angle and increment it by 0.5 degrees. Then, you clear the color and depth buffers.

Not applicable

Not applicable

    static float angle = 0.0f;
    angle += 0.5f * M_PI / 180.0f;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

You enable and bind the vertex information and the color information.

Not applicable

Not applicable

    glEnableVertexAttribArray(positionLoc);
    glBindBuffer(GL_ARRAY_BUFFER, vertexID);
    glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);

    glEnableVertexAttribArray(colorLoc);
    glBindBuffer(GL_ARRAY_BUFFER, colorID);
    glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);

Next, you need to apply the rotation of the angle variable to the matrix, effectively rotating on the y axis.

Not applicable

Not applicable

    matrix[0] = cos(angle);
    matrix[2] = -sin(angle);
    matrix[8] = sin(angle);
    matrix[10] = cos(angle);
    glUniformMatrix4fv(transformLoc, 1, false, matrix);

You call glDrawArrays() to draw the arrays with a triangle strip model, which is the same call you used in the OpenGL ES 1.1 template project. Then, you disable the vertex and color attribute arrays and swap the color buffers of the surface. To swap the color buffers, you call bbutil_swap(), just as you did in the OpenGL ES 1.1 sample app.

Not applicable

Not applicable

    glDrawArrays(GL_TRIANGLE_STRIP, 0 , 4);

    glDisableVertexAttribArray(positionLoc);
    glDisableVertexAttribArray(colorLoc);

    bbutil_swap();

Use your functions in main

You call initialize() and render() from main(). First, you set up EGL by calling bbutil_init_egl(), and then you can initialize your OpenGL ES model.

Not applicable

Not applicable

    if (EXIT_SUCCESS != bbutil_init_egl(screen_cxt)) {
        fprintf(stderr, "bbutil_init_egl failed\n");
        bbutil_terminate();
        screen_destroy_context(screen_cxt);
        return 0;
    }

    if (EXIT_SUCCESS != initialize()) {
        fprintf(stderr, "initialize failed\n");
        bbutil_terminate();
        screen_destroy_context(screen_cxt);
        return 0;
    }

In your main app loop, you call render() on each iteration to adjust the model on each frame.

Not applicable

Not applicable

    while (!exit_application) {
        /* Process events */
        ...
        render();
    }

Load a texture with a .png file

The bbutil API provides a bbutil_load_texture() function to load .png files into a texture to display to the active surface. You can use the PNG (libpng, libpng14, libpng16) library to load the .png file.

Read the .png file

To write a function to load a texture with a .png file, you must specify some parameters. You need the file path of the .png file to load, and the pointers for what your app returns: texture width, texture height, changed texture coordinates, and a handle to the texture.

Not applicable

Not applicable

int bbutil_load_texture(const char* filename, int* width, int* height, float* tex_x, float* tex_y, unsigned int *tex) 
{

First, you need to declare an 8-byte array for the .png header file. You also need to ensure that the texture handle is NULL, so that you don't overwrite an existing texture. Then, you open the .png file with fopen() and pass in rb as a parameter to specify that you want to read the file in binary format. After the file is open, you read the header file with fread() and test if it was a .png file.

Not applicable

Not applicable

    int i;
    GLuint format;
    /* Header for testing if the file is .png */
    png_byte header[8];

    if (!tex) {
        return EXIT_FAILURE;
    }

    /* Open the file as binary */
    FILE *fp = fopen(filename, "rb");
    if (!fp) {
        return EXIT_FAILURE;
    }

    /* Read the header */
    fread(header, 1, 8, fp);

    /* Test if the file is .png */
    int is_png = !png_sig_cmp(header, 0, 8);
    if (!is_png) {
        fclose(fp);
        return EXIT_FAILURE;
    }

Now, you need to declare some structures to hold the data in the .png file. The structures that you use are defined in the libpng library: png_structp and png_infop. You also check for errors each time you create the structures. If you don't check for errors, you need to call setjmp() to set up error handling. If you call setjmp() and pass in an actual value instead of a dereferenced pointer, you have a direct invocation. A dereferenced pointer uses the dereference operator (*) on a pointer variable to reference the value at the pointer address. So, in the case where you call setjmp() with a direct invocation, setjmp() returns 0, so you can continue.

Not applicable

Not applicable

    /* Create png struct */
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Create png info struct */
    png_infop info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Create png info struct */
    png_infop end_info = png_create_info_struct(png_ptr);
    if (!end_info) {
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Set up error handling (required without using custom error handlers above) */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        fclose(fp);
        return EXIT_FAILURE;
    }

Next, you initialize the default input and output functions for the .png file to standard C streams with a call to png_init_io(). You also specify that the first 8 bytes were already read. Then, you call png_read_info(), which reads all of the information up to the image data. This information contains the file details that you need. So, you declare the variables to extract and call png_get_IHDR().

Not applicable

Not applicable

    png_init_io(png_ptr, fp);

    png_set_sig_bytes(png_ptr, 8);

    png_read_info(png_ptr, info_ptr);

    int bit_depth, color_type;
    png_uint_32 image_width, image_height;

    png_get_IHDR(png_ptr, info_ptr, &image_width, &image_height, &bit_depth, &color_type, NULL, NULL, NULL);

Next, set the format type of the texture based on the color space of the .png file that you are using, such as RGB or RGBA, and update the information structure for the transformations that you requested. You need to know the row size, in bytes, to determine the buffer size, and you get the row size by calling png_get_rowbytes(). Using the row size in bytes, you allocate the buffer.

Not applicable

Not applicable

    switch (color_type)
    {
        case PNG_COLOR_TYPE_RGBA:
            format = GL_RGBA;
            break;
        case PNG_COLOR_TYPE_RGB:
            format = GL_RGB;
            break;
        default:
            fprintf(stderr,"Unsupported PNG color type (%d) for texture: %s", (int)color_type, filename);
            fclose(fp);
            png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
            return NULL;
    }

    png_read_update_info(png_ptr, info_ptr);

    int rowbytes = png_get_rowbytes(png_ptr, info_ptr);

    png_byte *image_data = (png_byte*) malloc(sizeof(png_byte) * rowbytes * image_height);

    if (!image_data) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        fclose(fp);
        return EXIT_FAILURE;
    }

You can use the memory addresses of the image data rows to read the .png file. You can allocate enough space for the row pointers, and then set the individual pointers to the correct offset in the image data rows. Call the png_read_image() function, and pass in the row pointers, to read the entire image data into png_ptr.

Not applicable

Not applicable

    png_bytep *row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * image_height);
    if (!row_pointers) {
        /* Clean up memory and close resources */
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        free(image_data);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Set the individual row_pointers to point at the correct offsets of image_data */
    for (i = 0; i < image_height; i++) {
        row_pointers[image_height - 1 - i] = image_data + i * rowbytes;
    }

    png_read_image(png_ptr, row_pointers);

Bind the image data to the texture

Now that the image data is ready, you can create your texture. Set the texture width and height to the next power of two from the original image dimensions, so that the image data has a border. Generate and bind the texture to the handle that was passed in.

Not applicable

Not applicable

    int tex_width, tex_height;

    tex_width = nextp2(image_width);
    tex_height = nextp2(image_height);

    glGenTextures(1, tex);
    glBindTexture(GL_TEXTURE_2D, (*tex));

Next, you specify the texture parameters that you want to use. For magnification or minification, use a weighted average of the surrounding pixels and indicate that you want the texture wrap coordinates to be clamped to the edge. You also specify that rows of pixel data are stored in a byte alignment.

Not applicable

Not applicable

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

Now, you can store the image data in the texture. If the texture width or height doesn't match the image, set the image data as a subimage of the main texture image. Otherwise, set it as the main texture image.

Not applicable

Not applicable

    if ((tex_width != image_width) || (tex_height != image_height) ) {
        glTexImage2D(GL_TEXTURE_2D, 0, format, tex_width, tex_height, 0, format, GL_UNSIGNED_BYTE, NULL);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, format, GL_UNSIGNED_BYTE, image_data);
    } else {
        glTexImage2D(GL_TEXTURE_2D, 0, format, tex_width, tex_height, 0, format, GL_UNSIGNED_BYTE, image_data);
    }

Now that the image data is set to the texture, start freeing the resources that you allocated. Check for any errors with OpenGL ES, free the memory that you don't need anymore, and destroy any handles that you don't need. If you don't receive any errors, return the other variables that were passed in: width, height, and changed texture coordinates.

Not applicable

Not applicable

    GLint err = glGetError();

    /* Clean up memory and close resources */
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
    free(image_data);
    free(row_pointers);
    fclose(fp);

    if (err == 0) {
        /* Return physical width and height of texture if pointers are not null */
        if(width) {
            *width = image_width;
        }
        if (height) {
            *height = image_height;
        }
        /* Return modified texture coordinates if pointers are not null */
        if(tex_x) {
            *tex_x = ((float) image_width - 0.5f) / ((float)tex_width);
        }
        if(tex_y) {
            *tex_y = ((float) image_height - 0.5f) / ((float)tex_height);
        }
        return EXIT_SUCCESS;
    } else {
        fprintf(stderr, "GL error %i \n", err);
        return EXIT_FAILURE;
    }
}

Use frame buffer objects

You can use frame buffer objects (FBOs) for flexible off-screen rendering, including rendering to a texture. FBOs contain a color, a depth, and a stencil buffer, similar to the default frame buffer in the Screen and Windowing API does. However, you can't directly access the default frame buffer, but you can directly access FBOs (for example, you can add an additional render buffer to the FBO). The contents of framebuffer objects are local to the shared object space of the current OpenGL ES rendering context, therefore no context switch is required.

Check if FBOs are available

Not all implementations support FBOs. You need to check the extensions to ensure that the extension is there. The extension to look for is GL_OES_framebuffer_object. To check for extensions, you call glGetString(GL_EXTENSIONS). Then, you parse the string for the extension that you're looking for or display all of the extensions to the console.

Not applicable

Not applicable

    const char* fbo_extension = "GL_OES_framebuffer_object";
    const GLubyte* ext =  glGetString(GL_EXTENSIONS);
    if( strstr( fbo_extension, (char *) ext) != NULL ){
    

You can call strstr() because the GL_OES_framebuffer_object constant is the only extension that matches the one that you're looking for. If the if statement doesn't evaluate to NULL, you can use FBOs.

Use a frame buffer object to render a texture

When you know that FBOs are available, you can create one using glGenFramebuffersOES(). First, you need a texture. You can use the same pipeline that is discussed in Texture compression. The texture variable is your handle to the texture, and image_data is your pointer to the image data for the texture. You pass the texture and image_data variables to glTexImage2D(), along with the internal format, texture width, texture height, and the texel format.

Not applicable

Not applicable

    glGenTextures( 1, &texture );
    glBindTexture( GL_TEXTURE_2d, texture );
    // set any texture parameters that you need
    glTexImage2D(GL_TEXTURE_2D, 0, internal_format, tex_width, tex_height, 0, format, GL_UNSIGNED_BYTE, image_data
    
    

Now, you generate the FBO. The fbo variable is your handle to the frame buffer object. After the FBO is generated, you bind it, which breaks all previous bindings.

Not applicable

Not applicable

    glGenFramebuffersOES( 1, &fbo );
    glBindFramebufferOES( GL_FRAMEBUFFER_OES, fbo );

The code sample above uses the OpenGL ES 1.1 extension API. OpenGL ES 2.0 and 3.0 have these functions included in the API, instead of as extensions, so the function calls and constants do not require the "OES".

After you set up the FBO, you can attach the texture that you want. You call glFramebufferTexture2DOES() and pass in the texture along with some constants. The constants specify that you want to target the FBO, and you attach the texture as a color buffer attachment, and using a 2-D texture.

Not applicable

Not applicable

    glFramebufferTexture2DOES( GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0 );

You can attach a depth render buffer to your FBO if you don't need to access the depth, such as in the code sample below. If you want to access the depth, consider using a depth texture instead.

Not applicable

Not applicable

    glGenRenderbufferOES( 1, &depth_renderbuffer );
    glBindRenderbufferOES( GL_RENDERBUFFER_OES, depth_renderbuffer );
    glRenderbufferStorageOES( GL_FRAMEBUFFER_OES, GL_DEPTH_COMPONENT24_OES, tex_width, tex_height );
   
    glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depth_renderbuffer );

Now that a depth render buffer is attached to your FBO, you can also check FBO status to make sure everything works as you expected.

Not applicable

Not applicable

    GLenum check = glCheckFrambufferStatusOES( GL_FRAMEBUFFER_OES );
    if( check == GL_FRAMEBUFFER_COMPLETE_OES )

If the if statement evaluates to true, you're good to go!

Clean up the FBO

After you finish, you need to clean up your FBO. Delete the texture and any render buffers that you attached, and then unbind the FBO and delete it.

Not applicable

Not applicable

    glDeleteTextures( 1, &texture );
    glDeleteRenderbuffersOES( 1, &depth_renderbuffer );
    glBindFramebufferOES( GL_FRAMEBUFFER_OES, NULL );
    glDeleteFramebuffersOES( 1, &fbo );

Use the Glview library

The Glview library, which is defined in glview.h, allows you to develop apps using OpenGL ES 1.1 and OpenGL ES 2.0. The Glview library is also part of the BlackBerry 10 Native SDK.

To learn more about functions in the Glview library, see the Glview API reference.

The Glview library does not currently support OpenGL ES 3.0.

Prerequisites

To use the Glview library, you need to include the Glview (libglview) library and the version of OpenGL ES (libGLES_CM, libGLESsoftgl, libGLESv1_CL, libGLESv1_CM, libGLESv2) library that you plan to use, as well as any additional libraries.

Use Glview to render a rotating cube

The rotating cube sample app uses the OpenGL ES 1.1 API to render the cube, and now you're going to implement it using the Glview library. The application logic and per-frame rendering remains the same as in the rotating cube sample app, so these samples focus on explaining the aspects related to the Glview library. For more detailed information about the sample, see the Tutorial: Create a basic 3-D app in C.

Start by including the required libraries. Remember to add the linker settings for the libraries that you include.

Not applicable

Not applicable

#include <glview/glview.h>

#include <GLES/gl.h>
#include <stdio.h>
#include <math.h>

The vertex and color data is the same as in Tutorial: Create a basic 3-D app in C.

Not applicable

Not applicable

static const GLfloat vertices[] =
{
      /* front */
      -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,

      /* right */
      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,

      /* back */
      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,

      /* left */
      -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,

      /* top */
      -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,

      /* bottom */
      -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
};

static const GLfloat colors[] =
{
       /* front  */  0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f,
       /* right  */  0.29296875f,0.66796875f,0.92578125f,1.0f,0.29296875f,0.66796875f,0.92578125f,1.0f, 0.29296875f,0.66796875f,0.92578125f,1.0f, 0.29296875f,0.66796875f,0.92578125f,1.0f, 0.29296875f,0.66796875f,0.92578125f,1.0f, 0.29296875f,0.66796875f,0.92578125f,1.0f,
       /* back   */  0.52734375f,0.76171875f,0.92578125f,1.0f, 0.52734375f,0.76171875f,0.92578125f,1.0f,0.52734375f,0.76171875f,0.92578125f,1.0f, 0.52734375f,0.76171875f,0.92578125f,1.0f, 0.52734375f,0.76171875f,0.92578125f,1.0f, 0.52734375f,0.76171875f,0.92578125f,1.0f,
       /* left   */  0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f,0.0625f,0.57421875f,0.92578125f,1.0f, 0.0625f,0.57421875f,0.92578125f,1.0f,
       /* top    */  0.29296875f,0.66796875f,0.92578125f,1.0f, 0.29296875f,0.66796875f,0.92578125f,1.0f,0.29296875f,0.66796875f,0.92578125f,1.0f,0.29296875f,0.66796875f,0.92578125f,1.0f,0.29296875f,0.66796875f,0.92578125f,1.0f,0.29296875f,0.66796875f,0.92578125f,1.0f,
       /* bottom */  0.52734375f,0.76171875f,0.92578125f,1.0f,0.52734375f,0.76171875f,0.92578125f,1.0f,0.52734375f,0.76171875f,0.92578125f,1.0f,0.52734375f,0.76171875f,0.92578125f,1.0f,0.52734375f,0.76171875f,0.92578125f,1.0f,0.52734375f,0.76171875f,0.92578125f,1.0f
};

For the initialize() and render() functions, you add the static qualifier to each of the functions to ensure that they exist for the life of the program. You should note one change in initialize(): instead of using glQuerySurface() to get the current surface dimensions, you call glview_get_size(), which eliminates some extra lines of code. Both initialize() and render() are the same as in Tutorial: Create a basic 3-D app in C.

Not applicable

Not applicable

static void initialize(void *p)
{
    unsigned int surface_height;
    unsigned int surface_width;
    glview_get_size(&surface_width, &surface_height);

    glClearDepthf(1.0f);
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glEnable(GL_CULL_FACE);
    glShadeModel(GL_SMOOTH);

    glViewport(0, 0, surface_width, surface_height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    float nearClip = -2.0f;
    float farClip  = 2.0f;
    float yFOV  = 75.0f;
    float yMax = nearClip * tan(yFOV*M_PI/360.0f);
    float aspect = surface_width/surface_height;
    float xMin = -yMax * aspect;
    float xMax = yMax *aspect;

    glFrustumf(xMin, xMax, -yMax, yMax, nearClip, farClip);
    glScalef((float)surface_height/(float)surface_width, 1.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

static void render(void *p)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);

    glEnableClientState(GL_COLOR_ARRAY);
    glColorPointer(4, GL_FLOAT, 0, colors);

    glRotatef(1.0f, 1.0f, 1.0f, 0.0f);

    glDrawArrays(GL_TRIANGLES, 0 , 36);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
}

The main function is smaller because Glview handles many background aspects that you explicitly handled before, such as EGL setup. First, you specify the per-frame rendering call and the rendering API that you're going to use with Glview by calling glview_initialize().

Not applicable

Not applicable

int main(int argc, char *argv[]) {

    glview_initialize(GLVIEW_API_OPENGLES_11, &display);

Then, you register the initialization function with Glview and start the main Glview execution loop.

Not applicable

Not applicable

    glview_register_initialize_callback(&initialize);
    glview_loop();
    return 0;
}

And, that's it! If you're interested in more information about the Glview library, see the Glview API reference.

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus