Mobile native Wearable native

OpenGL ES: Using OpenGL ES Graphics

This tutorial demonstrates how you can handle OpenGL ES graphics with the GLView component and EvasGL.

Warm-up

Become familiar with the OpenGL ES API basics by learning about:

Creating a Basic Application

This tutorial demonstrates how you can create a multicolored 3D rotating cube using the OpenGL ES 2.0 API provided by the GLView library. Several concepts are explained, such as the cube geometry, the initialization phase of the model, the adjustment of the model frame by frame, and the way to design the OpenGL ES rendering loop.

To create the basic application:

  1. Create a basic application as explained in the Hello World example.

    The basic UI application skeleton already makes available the window object that contains the GLView canvas.

  2. Build the environment:

    Define the application data structure that holds all the objects pertinent for the GLView application:

    • win: Application window
    • conform: Conformant object for the indicator
    • glview: GLView object
    • main_box: Box object which contains glview and inner_box
    • inner_box: Box object for the toolbox
    typedef struct 
    appdata 
    {
       Evas_Object *win;
       Evas_Object *conform;
       Evas_Object *glview;
    
       Evas_Object *main_box;
       Evas_Object *inner_box;
    } appdata_s;
    
  3. Create the OpenGL ES canvas:

    1. When developing an application with Elementary, you can create a window by using the Elementary utility function.

      To develop a GL application, you have to call the elm_config_accel_preference_set() function before creating a window which makes an application to use the GPU.

      elm_config_accel_preference_set("opengl"); 
      ad->win = elm_win_util_standard_add("GLView Example", "GLView Example");
    2. Create the GLView and prepare the application to call the GL functions:

      ad->glview = elm_glview_add(ad->main_box);

      There are 2 different methods to call GL functions:

      • Use Elementary GL Helper functions. You have to include the Elementary_GL_Helpers.h header file and define a global variable using ELEMENTARY_GLVIEW_GLOBAL_DEFINE(). Before calling GL functions, write ELEMENTARY_GLVIEW_GLOBAL_USE().

        This tutorial uses this method.

        #include <Elementary_GL_Helpers.h>
        ELEMENTARY_GLVIEW_GLOBAL_DEFINE();
        
        static void 
        create_gl_canvas(appdata_s *ad) 
        {
           ad->glview = elm_glview_add(ad->main_box);
           ELEMENTARY_GLVIEW_GLOBAL_USE(ad->glview);
        }
        
        static void 
        draw_gl(Evas_Object *obj) 
        { 
           glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        }
      • Get the Evas_GL instance from the elm_glview_gl_api_get() function, and call the OpenGL ES functions with the instance:

        ad->glview = elm_glview_add(ad->main_box);
        Evas_GL_API *glapi = elm_glview_gl_api_get(ad->glview);
        glapi->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    3. Set the GLView mode. The elm_glview_mode_set() function supports alpha, depth, stencil, MSAA, and client-side rotation.

      elm_glview_mode_set(ad->glview, ELM_GLVIEW_DEPTH);
    4. Set up callbacks:

      • Callback for initialization

        The initialization callback is called when the GLView is first created, after a valid OpenGL ES context and surface have been created. This is called from the main loop, just as the 3 other callbacks.

        elm_glview_init_func_set(ad->glview, init_gl);
        
      • Callback for resizing

        The resize callback is called whenever the GLView component is resized. A common action to take here is to reset the viewport.

        elm_glview_resize_func_set(ad->glview, resize_gl);
        
      • Callback for drawing

        The drawing callback is called whenever a new frame has to be drawn.

        elm_glview_render_func_set(ad->glview, draw_gl);
        

        The exact moment when this function is called depends on the policy set when calling.

        elm_glview_render_policy_set(glview, ELM_GLVIEW_RENDER_POLICY_ON_DEMAND);

        Another policy is ELM_GLVIEW_POLICY_ALWAYS, which always requests render, even when it is not visible. So the on demand policy is probably what you are looking for. The application can now draw anything using GL primitives when this callback is triggered. All draw operations are restricted to the surface of the GLView object previously created. Here it covers the whole window.

      • Callback for deleting

        The delete callback is triggered when the GLView is being destroyed, from the main loop, and no other callback can be called on the same object afterwards.

        elm_glview_del_func_set(ad->glview, del_gl);
        
    5. Add an animator.

      The application above is technically working but the scene does not get updated unless the object is marked as such. Games may want to use an animator to have a regular update of the scene.

      ad->ani = ecore_animator_add(animate_cb, ad->glview);

      Any other event, for example, user input, can be used to refresh the view if the view needs to be updated.

Creating a Cube

Creating and coloring the cube can be separated into 2 distinct tasks: define the vertices and then add the colors to the faces.

Figure: Cube

Cube

To create and color the cube:

  1. Declare an array that stores the vertices of the cube to make it look like the drawing above.

    static const float 
    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
    };
    

    Figure: Cube matrix

    Cube matrix

    Each triangle is defined with 3 point coordinates, 3 vertices for each triangle, 2 triangles per face and 6 faces. There are 36 vertices is total.

  2. Specify a color for each face of the cube. Each color is represented in the RGBA format for the corresponding vertex, where each component is ranged from 0 to 1 where 1 is the maximum value. For example, in 32-byte color space, the RGB color of (16, 147, 237) is translated as (0.0625, 0.57421875, 0.92578125). The A of RGBA stands for the alpha channel, which represents the transparency of the color. All colors defined in this tutorial are opaque to make it simpler, so each alpha value is set to 1.0. In this example, different variants of blue are used for the faces of the cube.

    Specify the colors of the cube into an array dedicated to this vertex:

    static const float 
    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
    };
    

Drawing the Cube with GLView

After the model is initialized, create functionality to manipulate the scene. OpenGL ES 2.0 provided by GLView requires more preliminary work than the previous version of the library, but it gives you more power and flexibility, although this example does not take much benefit.

To draw the cube with GLView and use mathematical functions for matrices:

  1. Declare additional global variables for tasks specific to OpenGL ES 2.0. A program object is needed, an identifier for the vertices buffer, and another for the colors. Variables are also required to ensure the connection with the shader language:

    • mvpLoc: Identifier for model-view-projection matrix
    • positionLoc: Identifier for the vertex position
    • colorLoc: Identifier for the vertex color

    Declare all these variables in the appdata object as static variables so that each function can use variables and that they exist for the whole duration of the program:

    typedef struct 
    appdata 
    {
       unsigned int program;
       unsigned int vtx_shader;
       unsigned int fgmt_shader;
       unsigned int vertexID;
       unsigned int colorID;
       unsigned int mvpLoc;
       unsigned int positionLoc;
       unsigned int colorLoc;
    }
    
  2. Since OpenGL ES 2.0, some functions for matrix transformations have been removed. Define 3 matrices (projection matrix, model-view matrix, and a combination of these) to allow you to perform any transformations on the initial vertices matrix.

    • Create the matrix multiplication function.

      Define a function that is able to return the inner product of 2 matrices. This function reproduces the behavior of the glMultMatrix() function available in OpenGL ES 1.1. This function is very useful since almost every matrix transformation can be translated as multiplications of matrices.

      The function takes 3 parameters, 1 is for the result and the other 2 matrices are operands.

      static void
      customMutlMatrix(float matrix[16], const float matrix0[16], const float matrix1[16])
      {
         int i, row, column;
         float temp[16];
         for (column = 0; column < 4; column++) 
         {
            for (row = 0; row < 4; row++) 
            {
               temp[column * 4 + row] = 0.0f;
               for (i = 0; i < 4; i++)
                  temp[column * 4 + row] += matrix0[i * 4 + row] * matrix1[column * 4 + i];
            }
         }
         for (i = 0; i < 16; i++)
            matrix[i] = temp[i];
      }
      
    • Create the matrix identity function.

      Implement a function equivalent to the glLoadIdentity() function that replaces the current matrix with the identity matrix.

      const float 
      unit_matrix[] = 
      {
         1.0f, 0.0f, 0.0f, 0.0f,
         0.0f, 1.0f, 0.0f, 0.0f,
         0.0f, 0.0f, 1.0f, 0.0f,
         0.0f, 0.0f, 0.0f, 1.0f
      }
      
      static void
      customLoadIdentity(float matrix[16])
      {
         for (int i = 0; i < 16; i++)
            matrix[i] = unit_matrix[i];
      }
      
    • Create the matrix projection function.

      Since the glFrustum() function has been deprecated, implement a function that produces perspective projection matrices that are used to transform from eye coordinate space to clip coordinate space. This matrix projects a portion of the space (the "frustum") to your screen. Many caveats apply (such as normalized device coordinates and perspective divide), but that is the idea.

      static int
      customFrustum(float result[16], 
                    const float left, const float right, 
                    const float bottom, const float top, 
                    const float near, const float far)
      {
         if ((right - left) == 0.0f || (top - bottom) == 0.0f || (far - near) == 0.0f) 
            return 0;
      
         result[0] = 2.0f / (right - left);
         result[1] = 0.0f;
         result[2] = 0.0f;
         result[3] = 0.0f;
      
         result[4] = 0.0f;
         result[5] = 2.0f / (top - bottom);
         result[6] = 0.0f;
         result[7] = 0.0f;
      
         result[8] = 0.0f;
         result[9] = 0.0f;
         result[10] = -2.0f / (far - near);
         result[11] = 0.0f;
      
         result[12] = -(right + left) / (right - left);
         result[13] = -(top + bottom) / (top - bottom);
         result[14] = -(far + near) / (far - near);
         result[15] = 1.0f;
      
         return 1;
      }
      
    • Create the matrix scaling function.

      The deprecated glScale() function represents a non-uniform scaling along the X, Y, and Z axes. The 3 parameters indicate the desired scale factor along each of the 3 axes.

      const float 
      scale_matrix[] = 
      {
         x, 0.0f, 0.0f, 0.0f,
         0.0f, y, 0.0f, 0.0f,
         0.0f, 0.0f, z, 0.0f,
         0.0f, 0.0f, 0.0f, 1.0f
      }
      

      The following example shows the implementation of the matrix scaling function:

      static void
      customScale(float matrix[16], const float sx, const float sy, const float sz)
      { 
         matrix[0]  *= sx; 
         matrix[1]  *= sx; 
         matrix[2]  *= sx; 
         matrix[3]  *= sx; 
      
         matrix[4]  *= sy; 
         matrix[5]  *= sy; 
         matrix[6]  *= sy; 
         matrix[7]  *= sy; 
      
         matrix[8]  *= sz; 
         matrix[9]  *= sz; 
         matrix[10] *= sz; 
         matrix[11] *= sz; 
      }
      
    • Create the matrix rotation function.

      Define a function to represent a rotation by the vector (X Y Z). The current matrix is multiplied by a rotation matrix.

      static void
      customRotate(float matrix[16], const float anglex, const float angley, const floatanglez)
      {
         const float pi = 3.141592f;
         float temp[16];
         float rz = 2.0f * pi * anglez / 360.0f;
         float rx = 2.0f * pi * anglex / 360.0f;
         float ry = 2.0f * pi * angley / 360.0f;
         float sy = sinf(ry);
         float cy = cosf(ry);
         float sx = sinf(rx);
         float cx = cosf(rx);
         float sz = sinf(rz);
         float cz = cosf(rz);
      
         customLoadIdentity(temp);
      
         temp[0] = cy * cz - sx * sy * sz;
         temp[1] = cz * sx * sy + cy * sz;
         temp[2] = -cx * sy;
      
         temp[4] = -cx * sz;
         temp[5] = cx * cz;
         temp[6] = sx;
      
         temp[8] = cz * sy + cy * sx * sz;
         temp[9] = -cy * cz * sx + sy * sz;
         temp[10] = cx * cy;
      
         customMultMatrix(matrix, matrix, temp);
      }
      
  3. Create the shader:
    1. Define the source for the shader using a string array. First build a vertex shader, which is used to a medium precision for float values. Then build a uniform matrix with dimensions 4x4 intended to hold the model-view-projection matrix. In addition, create 2 vector attributes which have 4 components for the vertex position and the color. Finally, the varying v_color variable can be accessed from the fragment shader.

      In the main function of the shader, initialize the position of the current vertex, gl_Position, with the product of the vertex position and the model-view-projection matrix, to normalize the position for the target screen. The pixel color is calculated by the varying variable from the vertex shader.

      In the fragment shader, declare a varying variable, and set the color of the pixel with this interpolated color.

      static const char 
      vertex_shader[] =
         "precision mediump float;"
         "uniform mat4 u_mvpMat;"
         "attribute vec4 a_position;"
         "attribute vec4 a_color;"
         "varying vec4 v_color;"
         "void main()"
         "{"
            "gl_Position = u_mvpMat * a_position;"
            "v_color = a_color;"
         "}";
      
      static const char 
      fragment_shader[] =
         "varying lowp vec4 v_color;"
         "void main()"
         "{"
            "gl_FragColor = v_color;"
         "}";
      
    2. Create the shaders, attach the source code defined above, and compile the program object:

      static void
      initShaders(void* data)
      {
         const char *p;
         appdata_s *ad = data;
      
         p = vertex_shader;
         ad->vtx_shader = glCreateShader(GL_VERTEX_SHADER);
         glShaderSource(ad->vtx_shader, 1, &p, NULL);
         glCompileShader(ad->vtx_shader);
      
         p = fragment_shader;
         ad->fgmt_shader = glCreateShader(GL_FRAGMENT_SHADER);
         glShaderSource(ad->fgmt_shader, 1, &p, NULL);
         glCompileShader(ad->fgmt_shader);
      
    3. Once the shaders are ready, instantiate the program object and link the shaders. If the linking succeeds, you can destroy the shaders afterwards (using the glDeleteShader() function). Since they are inside the program object, it is pointless to keep them in memory.

         ad->program = glCreateProgram();
      
         glAttachShader(ad->program, ad->vtx_shader);
         glAttachShader(ad->program, ad->fgmt_shader);
      
         glDeleteShader(ad->vtx_shader);
         glDeleteShader(ad->fgmt_shader);
      
         glLinkProgram(ad->program);
      
    4. For shader process, create identifiers for the attribute variables used in the shader program. Create an identifier for the model-view-projection matrix, another one for the current vertex position, and a last one for the vertex color.

         ad->mvpLoc = glGetUniformLocation(ad->program, "u_mvpMat");
         ad->positionLoc = glGetAttribLocation(ad->program, "a_position");
         ad->colorLoc = glGetAttribLocation(ad->program, "a_color");
      
    5. Generate the buffers for the vertex positions and colors:

         glGenBuffers(1, &ad->vertexID);
         glBindBuffer(GL_ARRAY_BUFFER, ad->vertexID);
         glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
      
         glGenBuffers(1, &ad->colorID);
         glBindBuffer(GL_ARRAY_BUFFER, ad->colorID);
         glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
      }
      
    6. Allocate memory for the matrix and load a unit matrix into it. Define the value that is used to build the perspective projection matrix with the customFrustum() function. Multiply this resulting matrix with a resizing matrix, so the model is correctly adjusted to the screen.

      float aspect;
      customLoadIdentity(view);
      
      if (w > h) 
      {
         aspect = (float)w/h;
         customFrustum(view, -1.0 * aspect, 1.0 * aspect, -1.0, 1.0, -1.0, 1.0);
      }
      else 
      {
         aspect = (float)h/w;
         customFrustum(view, -1.0, 1.0, -1.0 * aspect, 1.0 * aspect, -1.0, 1.0);
      }
      

Rendering the Cube

To render the cube:

  1. Set the viewport at 0,0 corresponding to the bottom left edge of the window, and the height and width of the GL surface. Clear the depth and the color buffers to the values that were selected during initialization. Call the glUseProgram() function to trigger the shader program.

    glViewport(0, 0, w, h);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glUseProgram(ad->program);
    
  2. Bind the position and color identifiers to the buffers defined above:

    glEnableVertexAttribArray(ad->positionLoc);
    glBindBuffer(GL_ARRAY_BUFFER, ad->vertexID);
    glVertexAttribPointer(ad->positionLoc, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    
    glEnableVertexAttribArray(ad->colorLoc);
    glBindBuffer(GL_ARRAY_BUFFER, ad->colorID);
    glVertexAttribPointer(ad->colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
    
  3. Initialize and calculate the transformation matrix of the model-view matrix by calling the customRotate() function. This makes the cube view rotate a little for a better perspective. Once the model-view matrix is ready to display, multiply the projection matrix with the model-view matrix.

    customLoadIdentity(model);
    customRotate(ad->model, 45.0f, 45.0f, 0.0f);
    customMutlMatrix(ad->mvp, ad->view, ad->model);
    
  4. Load the model-view-projection matrix into the shader and call the glDrawArrays() function to draw the model:

    glUniformMatrix4fv(ad->mvpLoc, 1, GL_FALSE, mvp);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glFlush();
    

Figure: Static cube

Static cube

Animating the Cube

To animate the cube:

  1. Use the Ecore_Animator to create an animation.

    The animator callback function is also triggered when the display is off. Use the ecore_animator_freeze() and ecore_animator_thaw() functions in the app_pause_cb and app_resume_cb callbacks for power saving.

    static Eina_Bool
    animate_cb(void *data)
    {
       elm_glview_changed_set(data);
    
       return EINA_TRUE;
    }
    
    static void
    create_gl_canvas(appdata_s *ad)
    {
       ad->ani = ecore_animator_add(animate_cb, ad->glview);
    }
    
    static void 
    app_pause(void *data) 
    {
       appdata_s *ad = data;
       ecore_animator_freeze(ad->ani);
    }
    
    static void 
    app_resume(void *data) 
    {
       appdata_s *ad = data;
       ecore_animator_thaw(ad->ani);
    }
    
  2. Define the global variables which are used as parameters of the rendering process. Add parameters to the application data object that are used to control the scaling and the rotation of the cube. Make the cube rotate on 1 axis (Z), and allow the user to interact with the mouse to make the cube rotate on the other axes (X and Y). To figure out whether the user is holding the mouse down, add a Boolean variable to have this information. Operations, such as shader initialization or program compilation, are not required at each tick of the animation loop. For better performance, isolate such tasks from the repetitive rendering loop. For such purpose, add a Boolean variable which tells whether the initialization is already done.

    typedef struct 
    appdata 
    {
       float xangle;
       float yangle;
       float zangle;
       Eina_Bool mouse_down : 1;
       Eina_Bool initialized : 1;
    }  appdata_s;
    
  3. Modify the rendering loop for animation.

    • Lighten the recurrent rendering process by adding an initialization step:

      if (ad->initialized) 
      {
         initShaders(ad);
         ad->initialized = EINA_TRUE;
      }
      
    • Before drawing the vertices, the rotation angle for the model-view matrix must be incremented for every tick. This makes the cube rotate automatically.

      customLoadIdentity(ad->model);
      customRotate(ad->model, ad->xangle, ad->yangle, ad->zangle++);
      customMutlMatrix(ad->mvp, ad->view, ad->model);
      
  4. Use the mouse to drag the cube around. To do so, add callbacks for mouse events. The first callback defines whether the user is holding the mouse down while moving the cursor around.

    static void
    mouse_down_cb(void *data, Evas *e , Evas_Object *obj , void *event_info)
    {
       appdata_s *ad = data;
       ad->mouse_down = EINA_TRUE;
    }
    
    static void
    mouse_up_cb(void *data, Evas *e , Evas_Object *obj , void *event_info)
    {
       appdata_s *ad = data;
       ad->mouse_down = EINA_FALSE;
    }
    

    When the mouse is down, calculate the new rotation angle with the mouse movement along the X and Y axis:

    static void
    mouse_move_cb(void *data, Evas *e , Evas_Object *obj , void *event_info)
    {
       Evas_Event_Mouse_Move *ev;
       ev = (Evas_Event_Mouse_Move *)event_info;
       appdata_s *ad = data;
       float dx = 0, dy = 0;
    
       if (ad->mouse_down) 
       {
          dx = ev->cur.canvas.x - ev->prev.canvas.x;
          dy = ev->cur.canvas.y - ev->prev.canvas.y;
          ad->xangle += dy;
          ad->yangle += dx;
       }
    }
    
  5. Define the mouse event callbacks when creating the image canvas:

    evas_object_event_callback_add(ad->glview, EVAS_CALLBACK_MOUSE_DOWN, mouse_down_cb, ad);
    evas_object_event_callback_add(ad->glview, EVAS_CALLBACK_MOUSE_UP, mouse_up_cb, ad);
    evas_object_event_callback_add(ad->glview, EVAS_CALLBACK_MOUSE_MOVE, mouse_move_cb, ad);
    

Implementing UI Component Interaction

To control some aspects of the rendering, implement UI component interaction (in this use case, sliders):

  1. Use sliders to control the shape of the cube. Declare 3 sliders to play with the scaling coordinates of the cube.

    typedef struct 
    appdata 
    {
       Evas_Object *slx;
       Evas_Object *sly;
       Evas_Object *slz;
    }
    
  2. Build and show the sliders when rendering the UI. The sliders accept values in a range from 0.0 to 1.5. They control the scaling of each axis of the cube.

    // Slider for X axis scale
    ad->slx = elm_slider_add(ad->inner_box);
    evas_object_size_hint_align_set(ad->slx, EVAS_HINT_FILL, 0);
    elm_slider_horizontal_set(ad->slx, EINA_TRUE);
    elm_slider_unit_format_set(ad->slx, "%1.2f units");
    elm_slider_indicator_format_set(ad->slx, "%1.2f units");
    elm_slider_indicator_show_set(ad->slx, EINA_TRUE);
    elm_slider_min_max_set(ad->slx, 0, 1.5);
    elm_slider_value_set(ad->slx, 0.75);
    evas_object_color_set(ad->slx, 0.0, 0.0, 120, 255);
    elm_box_pack_end(ad->inner_box, ad->slx);
    evas_object_show(ad->slx);
    
    // Slider for Y axis scale
    ad->sly = elm_slider_add(ad->inner_box);
    evas_object_size_hint_align_set(ad->sly, EVAS_HINT_FILL, 0);
    elm_slider_horizontal_set(ad->sly, EINA_TRUE);
    elm_slider_unit_format_set(ad->sly, "%1.2f units");
    elm_slider_indicator_format_set(ad->sly, "%1.2f units");
    elm_slider_indicator_show_set(ad->sly, EINA_TRUE);
    elm_slider_min_max_set(ad->sly, 0, 1.5);
    elm_slider_value_set(ad->sly, 0.75);
    evas_object_color_set(ad->sly, 0.0, 0.0, 120, 255);
    elm_box_pack_end(ad->inner_box, ad->sly);
    evas_object_show(ad->sly);
    
    // Slider for Z axis scale
    ad->slz = elm_slider_add(ad->inner_box);
    evas_object_size_hint_align_set(ad->slz, EVAS_HINT_FILL, 0);
    elm_slider_horizontal_set(ad->slz, EINA_TRUE);
    elm_slider_unit_format_set(ad->slz, "%1.2f units");
    elm_slider_indicator_format_set(ad->slz, "%1.2f units");
    elm_slider_indicator_show_set(ad->slz, EINA_TRUE);
    elm_slider_min_max_set(ad->slz, 0, 1.5);
    elm_slider_value_set(ad->slz, 0.75);
    evas_object_color_set(ad->slz, 0.0, 0.0, 120, 255);
    elm_box_pack_end(ad->inner_box, ad->slz);
    evas_object_show(ad->slz);
    
  3. Use the actual sliders's values and pass them to the scaling function in the rendering loop:

    double scalex = elm_slider_value_get(ad->slx);
    double scaley = elm_slider_value_get(ad->sly);
    double scalez = elm_slider_value_get(ad->slz);
    
    customLoadIdentity(ad->model);
    customRotate(ad->model, ad->xangle, ad->yangle, ad->zangle++);
    customScale(ad->model, scalex, scaley, scalez);
    customMutlMatrix(ad->mvp, ad->view, ad->model);
    

Implementing Effects

To implement effects:

  1. Create a button that resets the scene by putting the background color to black and makes the cube bounce back to its original scale. Add the button to the application data object.

    typedef struct 
    appdata 
    {
       Evas_Object *button;
    }
    
  2. Add the button to the UI:

    elm_object_text_set(ad->button, "Reset");
    elm_box_pack_start(ad->reset_vbox, ad->button);
    evas_object_smart_callback_add(ad->button, "clicked", btn_reset_cb, ad);
    elm_box_pack_end(ad->inner_box, ad->button);
    evas_object_show(ad->button);
    
  3. Declare a callback that resets the variables that have influence on the drawing of the cube. In addition, animate the sliders when they get back to their original position using the Ecore_Animator.

    typedef struct 
    appdata 
    {
       float slx_value;
       float sly_value;
       float slz_value;
    } appdata_s;
    
    static Eina_Bool
    animate_reset_cb(void *data, double pos)
    {
       appdata_s *ad = data;
       double frame = pos;
       float x, y, z;
    
       frame = ecore_animator_pos_map(pos, ECORE_POS_MAP_BOUNCE, 1.8, 7);
       x = ad->slx_value*(1-frame) + 0.75*frame;
       y = ad->sly_value*(1-frame) + 0.75*frame;
       z = ad->slz_value*(1-frame) + 0.75*frame;
    
       elm_slider_value_set(ad->slx, x);
       elm_slider_value_set(ad->sly, y);
       elm_slider_value_set(ad->slz, z);
    
       return EINA_TRUE;
    }
    
    static void
    btn_reset_cb(void *data, Evas *e , Evas_Object *obj , void *event_info)
    {
       appdata_s *ad = data;
       ad->slx_value = elm_slider_value_get(ad->slx);
       ad->sly_value = elm_slider_value_get(ad->sly);
       ad->slz_value = elm_slider_value_get(ad->slz);
       ecore_animator_timeline_add(1, animate_reset_cb, ad);
    }
    

Viewing the Entire Cube Source

The following code snippet contains the full code of the cube example. The details are explained in the other use cases.

// Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved

#include <app.h>
#include <Elementary.h>
#include <Elementary_GL_Helpers.h>
#include <efl_extension.h>
#include <dlog.h>

#if !defined(PACKAGE)
#define PACKAGE "org.tizen.glviewtutorial"
#endif

ELEMENTARY_GLVIEW_GLOBAL_DEFINE();

typedef struct 
appdata 
{
   // Elm_UI components
   Evas_Object *win;
   Evas_Object *conform;
   Evas_Object *glview;
   Evas_Object *main_box;
   Evas_Object *inner_box;
   Evas_Object *slx;
   Evas_Object *sly;
   Evas_Object *slz;
   Evas_Object *button;
   Ecore_Animator *ani;

   unsigned int program;
   unsigned int vtx_shader;
   unsigned int fgmt_shader;
   unsigned int vertexID;
   unsigned int colorID;
   unsigned int mvpLoc;
   unsigned int positionLoc;
   unsigned int colorLoc;
   float model[16], mvp[16];
   float view[16];
   float xangle;
   float yangle;
   float zangle;
   Eina_Bool mouse_down : 1;
   Eina_Bool initialized : 1;

   float slx_value;
   float sly_value;
   float slz_value;
} appdata_s;


static const float 
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 float 
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
};

// Vertex shader source
static const char 
vertex_shader[] =
   "precision mediump float;"
   "uniform mat4 u_mvpMat;"
   "attribute vec4 a_position;"
   "attribute vec4 a_color;"
   "varying vec4 v_color;"
   "void main()"
   "{"
   "   gl_Position = u_mvpMat * a_position;"
   "   v_color = a_color;"
   "}";

// Fragment shader source
static const char 
fragment_shader[] =
   "varying lowp vec4 v_color;"
   "void main()"
   "{"
   "   gl_FragColor = v_color;"
   "}";

static void
customLoadIdentity(float matrix[16])
{
   matrix[0] = 1.0f;
   matrix[1] = 0.0f;
   matrix[2] = 0.0f;
   matrix[3] = 0.0f;

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

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

   matrix[12] = 0.0f;
   matrix[13] = 0.0f;
   matrix[14] = 0.0f;
   matrix[15] = 1.0f;
}

static void
customMutlMatrix(float matrix[16], const float matrix0[16], const float matrix1[16])
{
   int i, row, column;
   float temp[16];
   for (column = 0; column < 4; column++) 
   {
      for (row = 0; row < 4; row++) 
      {
         temp[column * 4 + row] = 0.0f;
         for (i = 0; i < 4; i++)
            temp[column * 4 + row] += matrix0[i * 4 + row] * matrix1[column * 4 + i];
      }
   }
   for (i = 0; i < 16; i++)
      matrix[i] = temp[i];
}

static void
customScale(float matrix[16], const float sx, const float sy, const float sz)
{
   matrix[0]  *= sx;
   matrix[1]  *= sx;
   matrix[2]  *= sx;
   matrix[3]  *= sx;

   matrix[4]  *= sy;
   matrix[5]  *= sy;
   matrix[6]  *= sy;
   matrix[7]  *= sy;

   matrix[8]  *= sz;
   matrix[9]  *= sz;
   matrix[10] *= sz;
   matrix[11] *= sz;
}

static void
customRotate(float matrix[16], const float anglex, const float angley, const float anglez)
{
   const float pi = 3.141592f;
   float temp[16];
   float rz = 2.0f * pi * anglez / 360.0f;
   float rx = 2.0f * pi * anglex / 360.0f;
   float ry = 2.0f * pi * angley / 360.0f;
   float sy = sinf(ry);
   float cy = cosf(ry);
   float sx = sinf(rx);
   float cx = cosf(rx);
   float sz = sinf(rz);
   float cz = cosf(rz);

   customLoadIdentity(temp);

   temp[0] = cy * cz - sx * sy * sz;
   temp[1] = cz * sx * sy + cy * sz;
   temp[2] = -cx * sy;

   temp[4] = -cx * sz;
   temp[5] = cx * cz;
   temp[6] = sx;

   temp[8] = cz * sy + cy * sx * sz;
   temp[9] = -cy * cz * sx + sy * sz;
   temp[10] = cx * cy;

   customMutlMatrix(matrix, matrix, temp);
}

static int
customFrustum(float result[16], const float left, const float right, const float bottom, const float top, const float near, const float far)
{
   if ((right - left) == 0.0f || (top - bottom) == 0.0f || (far - near) == 0.0f) return 0;

   result[0] = 2.0f / (right - left);
   result[1] = 0.0f;
   result[2] = 0.0f;
   result[3] = 0.0f;

   result[4] = 0.0f;
   result[5] = 2.0f / (top - bottom);
   result[6] = 0.0f;
   result[7] = 0.0f;

   result[8] = 0.0f;
   result[9] = 0.0f;
   result[10] = -2.0f / (far - near);
   result[11] = 0.0f;

   result[12] = -(right + left) / (right - left);
   result[13] = -(top + bottom) / (top - bottom);
   result[14] = -(far + near) / (far - near);
   result[15] = 1.0f;

   return 1;
}

static void
initShaders(void* data)
{
   const char *p;
   appdata_s *ad = data;

   p = vertex_shader;
   ad->vtx_shader = glCreateShader(GL_VERTEX_SHADER);
   glShaderSource(ad->vtx_shader, 1, &p, NULL);
   glCompileShader(ad->vtx_shader);

   p = fragment_shader;
   ad->fgmt_shader = glCreateShader(GL_FRAGMENT_SHADER);
   glShaderSource(ad->fgmt_shader, 1, &p, NULL);
   glCompileShader(ad->fgmt_shader);

   ad->program = glCreateProgram();

   glAttachShader(ad->program, ad->vtx_shader);
   glAttachShader(ad->program, ad->fgmt_shader);

   glDeleteShader(ad->vtx_shader);
   glDeleteShader(ad->fgmt_shader);

   glLinkProgram(ad->program);

   ad->mvpLoc     = glGetUniformLocation(ad->program, "u_mvpMat");
   ad->positionLoc = glGetAttribLocation(ad->program, "a_position");
   ad->colorLoc   = glGetAttribLocation(ad->program, "a_color");
}

static void 
init_gl(Evas_Object *obj) 
{
   int w, h;
   appdata_s *ad = evas_object_data_get(obj, "ad");

   elm_glview_size_get(obj, &w, &h);

   if (!ad->initialized) 
   {
      initShaders(ad);

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

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

      ad->initialized = EINA_TRUE;
   }
}

void 
resize_gl(Evas_Object *obj) 
{
   int w, h;
   float aspect;
   appdata_s *ad = evas_object_data_get(obj, "ad");

   elm_glview_size_get(obj, &w, &h);
   customLoadIdentity(ad->view);

   if (w > h) 
   {
      aspect = (float) w / h;
      customFrustum(ad->view, -1.0 * aspect, 1.0 * aspect, -1.0, 1.0, -1.0, 1.0);
   } 
   else 
   {
      aspect = (float) h / w;
      customFrustum(ad->view, -1.0, 1.0, -1.0 * aspect, 1.0 * aspect, -1.0, 1.0);
   }
}

static void 
draw_gl(Evas_Object *obj) 
{
   int w, h;
   appdata_s *ad = evas_object_data_get(obj, "ad");

   double scalex = elm_slider_value_get(ad->slx);
   double scaley = elm_slider_value_get(ad->sly);
   double scalez = elm_slider_value_get(ad->slz);

   elm_glview_size_get(obj, &w, &h);

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

   glViewport(0, 0, w, h);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glUseProgram(ad->program);

   glEnableVertexAttribArray(ad->positionLoc);
   glBindBuffer(GL_ARRAY_BUFFER, ad->vertexID);
   glVertexAttribPointer(ad->positionLoc, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);

   glEnableVertexAttribArray(ad->colorLoc);
   glBindBuffer(GL_ARRAY_BUFFER, ad->colorID);
   glVertexAttribPointer(ad->colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

   customLoadIdentity(ad->model);
   customRotate(ad->model, ad->xangle, ad->yangle, ad->zangle++);
   customScale(ad->model, scalex, scaley, scalez);
   customMutlMatrix(ad->mvp, ad->view, ad->model);

   glUniformMatrix4fv(ad->mvpLoc, 1, GL_FALSE, ad->mvp);
   glDrawArrays(GL_TRIANGLES, 0, 36);

   glFlush();
}

static void 
del_gl(Evas_Object *obj) 
{
   appdata_s *ad = evas_object_data_get(obj, "ad");

   glDeleteShader(ad->vtx_shader);
   glDeleteShader(ad->fgmt_shader);
   glDeleteProgram(ad->program);

   evas_object_data_del((Evas_Object*) obj, "ad");
}

static Eina_Bool 
animate_cb(void *data) 
{
   elm_glview_changed_set(data);

   return EINA_TRUE;
}

static void 
del_animate_cb(void *data, Evas *evas, Evas_Object *obj, void *event_info) 
{
   appdata_s *ad = data;
   ecore_animator_del(ad->ani);
}

static void 
mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) 
{
   appdata_s *ad = data;
   ad->mouse_down = EINA_TRUE;
}

static void 
mouse_move_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) 
{
   Evas_Event_Mouse_Move *ev;
   ev = (Evas_Event_Mouse_Move *) event_info;
   appdata_s *ad = data;
   float dx = 0, dy = 0;

   if (ad->mouse_down) 
   {
      dx = ev->cur.canvas.x - ev->prev.canvas.x;
      dy = ev->cur.canvas.y - ev->prev.canvas.y;
      ad->xangle += dy;
      ad->yangle += dx;
   }
}

static void 
mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) 
{
   appdata_s *ad = data;
   ad->mouse_down = EINA_FALSE;
}

static Eina_Bool 
animate_reset_cb(void *data, double pos) 
{
   appdata_s *ad = data;
   double frame = pos;
   float x, y, z;

   frame = ecore_animator_pos_map(pos, ECORE_POS_MAP_BOUNCE, 1.8, 7);
   x = ad->slx_value * (1 - frame) + 0.75 * frame;
   y = ad->sly_value * (1 - frame) + 0.75 * frame;
   z = ad->slz_value * (1 - frame) + 0.75 * frame;

   elm_slider_value_set(ad->slx, x);
   elm_slider_value_set(ad->sly, y);
   elm_slider_value_set(ad->slz, z);

   return EINA_TRUE;
}

static void 
btn_reset_cb(void *data, Evas_Object *obj,	void *event_info) 
{
   appdata_s *ad = data;
   ad->slx_value = elm_slider_value_get(ad->slx);
   ad->sly_value = elm_slider_value_get(ad->sly);
   ad->slz_value = elm_slider_value_get(ad->slz);
   ecore_animator_timeline_add(1, animate_reset_cb, ad);
}

static void 
win_back_cb(void *data, Evas_Object *obj, void *event_info) 
{
   appdata_s *ad = data;
   // Let the window go to the hidden state
   elm_win_lower(ad->win);
}

static void
win_delete_request_cb(void *data, Evas_Object *obj, void *event_info)
{
   ui_app_exit();
}

static void 
create_base_gui(appdata_s *ad) 
{
   // Window
   elm_config_accel_preference_set("opengl");
   ad->win = elm_win_util_standard_add(PACKAGE, PACKAGE);
   elm_win_autodel_set(ad->win, EINA_TRUE);

   if (elm_win_wm_rotation_supported_get(ad->win)) 
   {
      int rots[4] = { 0, 90, 180, 270 };
      elm_win_wm_rotation_available_rotations_set(ad->win, (const int *) (&rots), 4);
   }

   evas_object_smart_callback_add(ad->win, "delete,request", win_delete_request_cb, NULL);
   eext_object_event_callback_add(ad->win, EEXT_CALLBACK_BACK, win_back_cb, ad);

   elm_win_conformant_set(ad->win, EINA_TRUE);
   elm_win_indicator_mode_set(ad->win, ELM_WIN_INDICATOR_SHOW);
   elm_win_indicator_opacity_set(ad->win, ELM_WIN_INDICATOR_TRANSPARENT);

   ad->conform = elm_conformant_add(ad->win);
   evas_object_size_hint_weight_set(ad->conform, EVAS_HINT_EXPAND,	EVAS_HINT_EXPAND);
   elm_win_resize_object_add(ad->win, ad->conform);
   evas_object_show(ad->conform);

   ad->main_box = elm_box_add(ad->conform);
   evas_object_size_hint_weight_set(ad->main_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_show(ad->main_box);

   elm_object_content_set(ad->conform, ad->main_box);

   // Show the window after the base GUI is set up
   evas_object_show(ad->win);
}

static void 
create_gl_canvas(appdata_s *ad) 
{
   // Create and initialize GLView
   ad->glview = elm_glview_add(ad->main_box);
   ELEMENTARY_GLVIEW_GLOBAL_USE(ad->glview);
   evas_object_size_hint_align_set(ad->glview, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_size_hint_weight_set(ad->glview, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);

   // Request a surface with alpha and a depth buffer
   elm_glview_mode_set(ad->glview, ELM_GLVIEW_DEPTH);

   // The resize policy tells GLView what to do with the surface when it
   // resizes. ELM_GLVIEW_RESIZE_POLICY_RECREATE tells it to
   // destroy the current surface and recreate it to the new size
   elm_glview_resize_policy_set(ad->glview, ELM_GLVIEW_RESIZE_POLICY_RECREATE);

   // The render policy sets how GLView must render GL code.
   // ELM_GLVIEW_RENDER_POLICY_ON_DEMAND has the GL callback
   // called only when the object is visible.
   // ELM_GLVIEW_RENDER_POLICY_ALWAYS causes the callback to be
   // called even if the object were hidden
   elm_glview_render_policy_set(ad->glview, ELM_GLVIEW_RENDER_POLICY_ON_DEMAND);

   // The initialize callback function gets registered here
   elm_glview_init_func_set(ad->glview, init_gl);

   // The delete callback function gets registered here
   elm_glview_del_func_set(ad->glview, del_gl);

   // The resize callback function gets registered here
   elm_glview_resize_func_set(ad->glview, resize_gl);

   // The render callback function gets registered here
   elm_glview_render_func_set(ad->glview, draw_gl);

   // Add the GLView to the box and show it
   elm_box_pack_end(ad->main_box, ad->glview);
   evas_object_show(ad->glview);

   // This adds an animator so that the app regularly
   // triggers updates of the GLView using elm_glview_changed_set()
   //
   // NOTE: If you delete GL, this animator keeps running trying to access
   // GL so this animator needs to be deleted with ecore_animator_del()
   ad->ani = ecore_animator_add(animate_cb, ad->glview);
   evas_object_data_set(ad->glview, "ad", ad);
   evas_object_event_callback_add(ad->glview, EVAS_CALLBACK_DEL, del_animate_cb, ad->glview);
   evas_object_event_callback_add(ad->glview, EVAS_CALLBACK_MOUSE_DOWN, mouse_down_cb, ad);
   evas_object_event_callback_add(ad->glview, EVAS_CALLBACK_MOUSE_UP, mouse_up_cb, ad);
   evas_object_event_callback_add(ad->glview, EVAS_CALLBACK_MOUSE_MOVE, mouse_move_cb, ad);

   // Set rotation variables
   ad->xangle = 45.0f;
   ad->yangle = 45.0f;
   ad->zangle = 0.0f;
   ad->mouse_down = EINA_FALSE;
   ad->initialized = EINA_FALSE;
}

static void 
create_toolbox(appdata_s *ad) 
{
   ad->inner_box = elm_box_add(ad->main_box);
   evas_object_size_hint_align_set(ad->inner_box, EVAS_HINT_FILL, 0);
   elm_box_horizontal_set(ad->inner_box, EINA_FALSE);
   elm_box_homogeneous_set(ad->inner_box, EINA_FALSE);
   elm_box_pack_end(ad->main_box, ad->inner_box);
   evas_object_show(ad->inner_box);

   // Slider for X axis scale
   ad->slx = elm_slider_add(ad->inner_box);
   evas_object_size_hint_align_set(ad->slx, EVAS_HINT_FILL, 0);
   elm_slider_horizontal_set(ad->slx, EINA_TRUE);
   elm_slider_unit_format_set(ad->slx, "%1.2f units");
   elm_slider_indicator_format_set(ad->slx, "%1.2f units");
   elm_slider_indicator_show_set(ad->slx, EINA_TRUE);
   elm_slider_min_max_set(ad->slx, 0, 1.5);
   elm_slider_value_set(ad->slx, 0.75);
   evas_object_color_set(ad->slx, 0.0, 0.0, 120, 255);
   elm_box_pack_end(ad->inner_box, ad->slx);
   evas_object_show(ad->slx);

   // Slider for Y axis scale
   ad->sly = elm_slider_add(ad->inner_box);
   evas_object_size_hint_align_set(ad->sly, EVAS_HINT_FILL, 0);
   elm_slider_horizontal_set(ad->sly, EINA_TRUE);
   elm_slider_unit_format_set(ad->sly, "%1.2f units");
   elm_slider_indicator_format_set(ad->sly, "%1.2f units");
   elm_slider_indicator_show_set(ad->sly, EINA_TRUE);
   elm_slider_min_max_set(ad->sly, 0, 1.5);
   elm_slider_value_set(ad->sly, 0.75);
   evas_object_color_set(ad->sly, 0.0, 0.0, 120, 255);
   elm_box_pack_end(ad->inner_box, ad->sly);
   evas_object_show(ad->sly);

   // Slider for Z axis scale
   ad->slz = elm_slider_add(ad->inner_box);
   evas_object_size_hint_align_set(ad->slz, EVAS_HINT_FILL, 0);
   elm_slider_horizontal_set(ad->slz, EINA_TRUE);
   elm_slider_unit_format_set(ad->slz, "%1.2f units");
   elm_slider_indicator_format_set(ad->slz, "%1.2f units");
   elm_slider_indicator_show_set(ad->slz, EINA_TRUE);
   elm_slider_min_max_set(ad->slz, 0, 1.5);
   elm_slider_value_set(ad->slz, 0.75);
   evas_object_color_set(ad->slz, 0.0, 0.0, 120, 255);
   elm_box_pack_end(ad->inner_box, ad->slz);
   evas_object_show(ad->slz);

   // Reset button
   ad->button = elm_button_add(ad->inner_box);
   elm_object_text_set(ad->button, "Reset");
   evas_object_smart_callback_add(ad->button, "clicked", btn_reset_cb, ad);
   evas_object_size_hint_align_set(ad->button, EVAS_HINT_FILL, 0);
   elm_box_pack_end(ad->inner_box, ad->button);
   evas_object_show(ad->button);
}

static bool 
app_create(void *data) 
{
   // Hook to take necessary actions before main event loop starts
   // Initialize UI resources and application's data
   // If this function returns true, the main loop of application starts
   // If this function returns false, the application is terminated
   appdata_s *ad = data;

   create_base_gui(ad);
   create_gl_canvas(ad);
   create_toolbox(ad);

   return true;
}

static void 
app_control(app_control_h app_control, void *data) 
{
   // Handle the launch request
}

static void 
app_pause(void *data) 
{
   appdata_s *ad = data;
   ecore_animator_freeze(ad->ani);
}

static void 
app_resume(void *data) 
{
   appdata_s *ad = data;
   ecore_animator_thaw(ad->ani);
}

static void 
app_terminate(void *data) 
{
   // Release all resources
}

int 
main(int argc, char *argv[]) 
{
   appdata_s ad = { 0, };
   int ret = 0;
   ui_app_lifecycle_callback_s event_callback = { 0, };

   event_callback.create = app_create;
   event_callback.terminate = app_terminate;
   event_callback.pause = app_pause;
   event_callback.resume = app_resume;
   event_callback.app_control = app_control;

   ret = ui_app_main(argc, argv, &event_callback, &ad);
   if (ret != APP_ERROR_NONE) 
   {
      dlog_print(DLOG_ERROR, PACKAGE, "The application failed to start, and returned %d", ret);
   }

   return ret;
}

Using OpenGL ES Extensions

EvasGL, offering an abstraction layer above OpenGL ES, provides an easy mechanism to check for support and use OpenGL ES extensions:

  1. Detect support for an extension.

    In OpenGL ES, you must always call the glGetString(GL_EXTENSIONS) function. Make sure that the extension name is present in the list and then dynamically find the function pointer using the dlsym(), eglGetProcAddress(), or glXGetProcAddress() function.

    Since EvasGL exposes only a structure with the function pointers set to internal wrappers or the proper OpenGL ES implementation library, it can also expose all the detected extensions simply by setting their function pointers.

    To detect support for the GL_OES_get_program_binary extension or equivalent, and to get the function pointer associated:

    Evas_GL_API *gl = elm_glview_api_get(glview);
    
    // Check for support for the Program Binary OES extension
    if (gl->glGetProgramBinaryOES)
    {
       printf("Program binary extension is supported.\n");
    }
    
  2. Call an extension.

    Calling an extension is similar to calling a function:

    if (gl->glGetProgramBinaryOES)
    {
       char buf[4096];
       size_t len;
       Glenum fmt;
       gl->glGetProgramBinaryOES(prgid, sizeof(buf), &len, &fmt, buf);
    }
    

Using EvasGL Extensions

EvasGL is not only an abstraction layer on top of OpenGL ES, but also on top of EGL and GLX. As such, EvasGL tries to imitate EGL in a platform-independent manner, and exposes the underlying platform extensions when it can.

Image and sync support are the most commonly used EvasGL extensions. Both can be used for multi-thread rendering, but EvasGL images can also be used to share images between contexts.

  • To use the image extension:

    There are 2 versions of the evasglCreateImage() function, out of which extra Evas_GL_Context is taken as a parameter. It is recommended to call the evasglCreateImageForContext() function if you are dealing with multiple contexts, otherwise calling the evasglCreateImage() function is sufficient.

    1. Check for support.

      Before using this extension, check whether it is supported.

      if (gl->evasglCreateImageForContext && gl->evasglDestroyImage)
      {
         // Good...
      }
      
    2. Create an image.

      Create a render buffer and bind it to an EvasGL image.

      const int width = 64, height = 64;
      GLuint fbo, color_rb;
      EvasGLImage *image;
      Evas_GL *evgl;
      Evas_GL_Context *ctx;
      
      gl->GenFramebuffers(1, &fbo);
      gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo);
      gl->glGenRenderbuffers(1, &color_rb);
      gl->glBindRenderbuffer(GL_RENDERBUFFER_EXT, color_rb);
      gl->glRenderbufferStorage(GL_RENDERBUFFER_EXT, GL_RGBA, width, height);
      gl->glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
                                       GL_COLOR_ATTACHMENT0_EXT,
                                       GL_RENDERBUFFER_EXT,
                                       color_rb);
      
      evgl = elm_glview_evas_gl_get(glview);
      ctx = evas_gl_current_context_get(evgl);
      image = gl->evasglCreateImageForContext(evgl, ctx, EVAS_GL_TEXTURE_2D,
                                              (void *)(intptr_t) color_rb, NULL);
      

      The EvasGL image is now created and available for use from another context.

    3. Use an image.

      Draw something in the texture and render that texture to the screen. The following example shows how to skip the draw function.

      gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo);
      draw_scene(glview);

      You can also bind the image to a texture for display on the back buffer:

      GLuint tex;
      
      gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
      gl->glGenTextures(1, &tex);
      gl->glBindTexture(GL_TEXTURE_2D, tex);
      gl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
      // Draw the texture content on the screen
      static const GLint verts[12] = {-5, -6, -10,  5, -6, -10,  -5, 4, 10,  5, 4, 10};
      static const GLint tex_coords[8] = {0, 0,  1, 0,  0, 1,  1, 1};
      
      gl->glClearColor(0, 0, 0, 0);
      gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      gl->glEnable(GL_TEXTURE_2D);
      gl->glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
      gl->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);
      gl->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      gl->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      
      gl->glEnableClientState(GL_VERTEX_ARRAY);
      gl->glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      gl->glVertexPointer(3, GL_INT, 0, verts);
      gl->glTexCoordPointer(2, GL_INT, 0, tex_coords);
      
      gl->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
      
      gl->glDisableClientState(GL_VERTEX_ARRAY);
      gl->glDisableClientState(GL_COLOR_ARRAY);
      gl->glDisable(GL_TEXTURE_2D);
      

      The image content can be shared between different contexts.

    4. Destroy an image.

      After releasing all the associated resources, such as FBO and textures, release the image object itself.

      gl->evasglDestroyImage(image);
  • To use the sync extension:

    Another commonly used extension is the fence sync extension along with the reusable sync and wait sync. This allows creating a semaphore-style object that is released as soon as all the previous render operations have been completed.

    This guide does not explain the details of these extensions, as they must behave in a similar way to their EGL implementations.

    As usual with extensions, check the support:

    if (gl->evasglCreateSync)
       // fence_sync must be supported
    

Using Direct Rendering

To enhance rendering performance, the Direct Rendering option is supported.

  • To use direct rendering in GLView:

    In GLView, the ELM_GLVIEW_DIRECT option is one of GLView mode's enums and the option can be enabled using the elm_glview_mode_set() function.

    // Tizen 2.3
    // elm_config_accel_preference_set("opengl");
    
    // Since Tizen 2.3.1
    elm_config_accel_preference_set("opengl:depth24:stencil8:msaa_high");
    Evas_Object *win = elm_win_util_standard_add("sample", "sample");
    
    Evas_Object *glview = elm_glview_add(win);
    elm_glview_mode_set(glview, ELM_GLVIEW_DEPTH_24 | ELM_GLVIEW_STENCIL_8 | ELM_GLVIEW_MULTISAMPLE_HIGH);
    

    To use the Direct Rendering mode since Tizen 2.3.1, set the same option values (depth, stencil, and MSAA) to a rendering engine and a GLView object. You can set the option values to a rendering engine using the elm_config_accel_preference_set() function and to a GLView object using the elm_glview_mode_set() function. If the GLView object option values are bigger or higher than the rendering engine's, the Direct Rendering mode is disabled.

  • To use direct rendering in EvasGL:

    In EvasGL, the EVAS_GL_OPTIONS_DIRECT is one of EvasGL's config options and the option can be enabled by setting the Evas_GL_Config option.

    // Tizen 2.3
    // elm_config_accel_preference_set("opengl");
    
    // Since Tizen 2.3.1
    elm_config_accel_preference_set("opengl:depth24:stencil8:msaa_high");
    Evas_Object *win = elm_win_util_standard_add("sample", "sample");
    
    Evas_GL_Config *cfg = evas_gl_config_new();
    cfg = evas_gl_config_new();
     
    cfg->color_format = EVAS_GL_RGB_888;
    cfg->depth_bits = EVAS_GL_DEPTH_BIT_24;
    cfg->stencil_bits = EVAS_GL_STENCIL_BIT_8;
    cfg->options_bits = EVAS_GL_OPTIONS_DIRECT;
    cfg->multisample_bits = EVAS_GL_MULTISAMPLE_HIGH;
    

    To use the Direct Rendering mode since Tizen 2.3.1, set the same option values (depth, stencil, and MSAA) to a rendering engine and an Evas_GL_Config object. You can set the option values to a rendering engine using the elm_config_accel_preference_set() function. If the Evas_GL_Config object option values are bigger or higher than the rendering engine's, the Direct Rendering mode is disabled.

    Note
    If direct rendering is enabled, EvasGL renders directly to the back buffer of the window. Otherwise, EvasGL renders to the off screen buffer, then composited to the back buffer of the window.

    Although direct rendering is enabled, EvasGL not always renders directly to the back buffer. The following conditions disable direct rendering and force a fallback to indirect rendering in a frame buffer:

    • If the object's color is not 255,255,255,255.
    • If the object has an Evas map.
    • If the object size is different from the viewport (RESIZE_POLICY_SCALE).
    • If the window is rotated and CLIENT_SIDE_ROTATION is not set.
    • If the GLView policy is set to ALWAYS render or the EvasGL does not use pixel getter callback.
    Caution
    In the render callback function, call only GL functions.

    In case the GL functions are called outside the render callback function, you must call the evas_gl_make_current() function before the GL function calls. However, this results in a performance degradation due to context switching, and only works if the target surface is not an Evas_GL_Surface with Direct Rendering enabled.

    If the target buffer is an Evas_GL_Surface with Direct Rendering enabled, all GL functions must be called from the render callback function only. All other operations can break the rendering order and the unexpected rendering occurs.

Using Client-side Rotation

The Client Side Rotation is a special value that indicates to EFL that the application handles the view rotation when the device is rotated. This is needed only when the application requests Direct Rendering.

If the window is rotated and the Direct Rendering flag is set, Client Side Rotation can be used to avoid falling back to a frame buffer.

  • To use GLView rotation:

    In GLView, the ELM_GLVIEW_CLIENT_SIDE_ROTATION option is one of GLView mode's enums and the option can be enabled by using the elm_glview_mode_set() function. This option is needed only when Direct Rendering is enabled.

    Evas_Object *gl;
    gl = elm_glview_add(win);
     
    elm_glview_mode_set(gl, ELM_GLVIEW_DEPTH | ELM_GLVIEW_DIRECT | ELM_GLVIEW_CLIENT_SIDE_ROTATION);
  • To use EvasGL rotation:

    In EvasGL, the EVAS_GL_OPTIONS_CLIENT_SIDE_ROTATION is one of EvasGL's config options and this option can be enabled by setting the Evas_GL_Config option.

    Evas_GL_Config *cfg;
    cfg = evas_gl_config_new();
     
    cfg->options_bits = EVAS_GL_OPTIONS_DIRECT | EVAS_GL_OPTIONS_CLIENT_SIDE_ROTATION;

    Get the current rotation value:

    static void 
    _draw_gl(Evas_Object *obj) 
    {
       int w, h, rotation;
       elm_glview_size_get(obj, &w, &h);
       rotation = evas_gl_rotation_get(ad->evasgl);
    
       if (rotation % 180)
       {
          // Adjust gl size
       } 
    }

    To get the current rotation of the view, in degrees, call the evas_gl_rotation_get() function to properly handle the current rotation of the view. It always returns 0 unless the option EVAS_GL_OPTIONS_CLIENT_SIDE_ROTATION has been set. Indeed, in case of Direct Rendering to the back buffer, the client application is responsible for properly rotating its view. This can generally be done by applying a rotation to a view matrix.

Go to top