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 the Basic Application

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

First create a basic application as explained in the Basic application tutorial. This provides a basic UI application skeleton which already makes available the window object that contains the GLView canvas.

Building the Environment

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

  • Evas_Object *win: Application window
  • Evas_Object *conform: Conformant object for indicator
  • Evas_Object *glview: GLView object
  • Evas_Object *main_box: Box object which contains glview and inner_box
  • Evas_Object *inner_box: Box object for toolbox
typedef struct appdata 
{
   Evas_Object *win;
   Evas_Object *conform;
   Evas_Object *glview;

   Evas_Object *main_box;
   Evas_Object *inner_box;
} appdata_s;

OpenGL ES Canvas

When developing an application with Elementary, you can create a window by using the Elementary utility function as below:

elm_config_accel_preference_set("opengl"); 
ad->win = elm_win_util_standard_add("GLView Example", "GLView Example");

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

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

There are 2 different methods to call GL functions.

  • First is to use Elementary GL Helper functions. You have to include Elementary_GL_Helpers.h and define a global variable by using ELEMENTARY_GLVIEW_GLOBAL_DEFINE(). Before calling gl functions, write ELEMENTARY_GLVIEW_GLOBAL_USE(). This tutorial uses this method. The usage is as follows.
    #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);
    }
  • Second, you can get the Evas_GL instance from elm_glview_gl_api_get function, then you can 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);

The next thing to do is to set the GLView mode. elm_glview_mode_set(Evas_Object *obj, Elm_GLView_Mode mode) supports alpha, depth, stencil, MSAA, and client_side_rotation.

elm_glview_mode_set(ad->glview, ELM_GLVIEW_DEPTH);

To set up callbacks:

elm_glview_init_func_set(ad->glview, init_gl);
elm_glview_resize_func_set(ad->glview, resize_gl);
elm_glview_render_func_set(ad->glview, draw_gl);
elm_glview_del_func_set(ad->glview, del_gl);
ani = ecore_animator_add(animate_cb, ad->glview);
  • 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.

  • 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.

  • Callback for drawing

    The drawing callback is called whenever a new frame has to be drawn. 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 requests render always even when it is not visible. So 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.

  • Add an animator.

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

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

Creating the Cube

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

Figure: Cube

Cube

Declare an array that stores the vertices of the cube in order 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 three point coordinates, three vertices for each triangle, two triangles per face and six faces, so there are 36 vertices is total.

The next step is to 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

Mathematical Functions for Matrices

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

First, 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. Three variables are also required to ensure the connection with the shader language:

  • mvpLoc is an identifier for model-view-projection matrix.
  • positionLoc is an identifier for the vertex position.
  • colorLoc is an 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;
}

Since OpenGL ES 2.0, some functions for matrix transformations have been removed. Define three matrices to use: projection matrix, model-view matrix, and a combination of these allows you to perform any transformations on the initial vertices matrix.

Matrix Multiplication Function (glMultMatrix)

First, define a function that is able to return the inner product of two matrices. This function reproduces the behavior of glMultMatrix() 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 three arguments, one 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];
}

Matrix Identity Function (glLoadIdentity)

Implement a function equivalent to glLoadIdentity() 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];
}

Matrix Projection Function (glFrustum)

Since glFrustum has been depreciated, 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 "fustum") to your screen. Many caveats apply (normalized device coordinates, perspective divide, etc), 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;
}

Matrix Scaling Function (glScale)

Depreciated glScale() function represents a non-uniform scaling along the x, y, and z axes. The three parameters indicate the desired scale factor along each of the three 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
}

Here is 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; 
}

Matrix Rotation Function (glRotate)

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);
}

Create the Shader

Define the source for the shader using a string array. First comes out 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. Also create two vector attributes which have 4 components for the vertex position and the color. This varying variable v_color 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, in order 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, then 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;"
   "}";

Create the shaders, attach the source code that is just defined 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);

Once the shaders are ready, instantiate the program object and link the shaders. If the linking succeeds, you can destroy the shaders afterwards (using glDeleteShader). Since they are inside the program object, so 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);

For shader process, create identifiers for the attribute variables used in the shader program. First 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");

Finally, 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);
}

Allocate memory for the matrix and load a unit matrix into it. Then define the value that is used in order to build the perspective projection matrix. The customFrustum() function is used for it. 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

We 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. Then call the glUseProgram() function in order to trigger the shader program.

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

glUseProgram(ad->program);

Also 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);

After this, 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);

Then load the model-view-projection matrix into the shader and call glDrawArrays() to draw the model.

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

You now are the proud owner of a nice cube!

Figure: Static cube

Static cube

Animating the Cube

Ecore_Animator is used to create an animation.

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

   return EINA_TRUE;
}

static void
create_gl_canvas(appdata_s *ad)
{
   ani = ecore_animator_add(animate_cb, ad->glview);
}

Next 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. To make the cube rotate on one axis, take z, and allow the user to interact with the mouse to make the cube rotate on the two other axes x and y. In order 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 task 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;

Here are the modifications that must be done to the rendering loop for animation.

First, 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.

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

This makes the cube rotate automatically. The next thing is to 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;
   }
}

Define the mouse events 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

Consider using UI component to control some aspects of the rendering. For example, use some 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;
}

When rendering the GUI, build and show the sliders. Those accept values in a range from 0.0 to 1.5 and 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);

Then 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

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

typedef struct appdata 
{
   Evas_Object *button;
}

Then add the button to the GUI:

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);

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 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;

   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) 
{
   Ecore_Animator *ani = evas_object_data_get(obj, "ani");
   ecore_animator_del(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 window go to 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) 
{
   Ecore_Animator *ani;

   // 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 will tell 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 should render GL code.
   // ELM_GLVIEW_RENDER_POLICY_ON_DEMAND will have the GL callback
   // called only when the object is visible.
   // ELM_GLVIEW_RENDER_POLICY_ALWAYS would cause 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 will regularly
   // trigger updates of the GLView using elm_glview_changed_set()
   //
   // NOTE: If you delete GL, this animator will keep running trying to access
   // GL so this animator needs to be deleted with ecore_animator_del()
   ani = ecore_animator_add(animate_cb, ad->glview);
   evas_object_data_set(ad->glview, "ani", ani);
   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) 
{
   // Take necessary actions when application becomes invisible
}

static void app_resume(void *data) 
{
   // Take necessary actions when application becomes visible
}

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.

EvasGLImage

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

The code below is just an example.

  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.

    To draw something in the texture and render that texture to the screen. 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);
    And do the actual draw operation (draw the contents of the texture on 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);

Sync

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 should 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.

GLView

// 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);

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.

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.

EvasGL

// 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;

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. 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 will render directly to the back buffer of the window. Otherwise, EvasGL will render to the off screen buffer, then composited to the back buffer of the window.

Although direct rendering is enabled, EvasGL will not always render directly to the back buffer. Here are some conditions that will disable direct rendering and force a fallback to indirect rendering in a frame buffer.

  1. If the object's color is not 255,255,255,255.
  2. If the object has an Evas map.
  3. If the object size is different from the viewport, (RESIZE_POLICY_SCALE).
  4. If the window is rotated and CLIENT_SIDE_ROTATION is not set.
  5. 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 will occur.

Using Client-side Rotation

The Client Side Rotation is a special value that indicates to EFL that the application will handle 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.

GLView

Evas_Object *gl;
gl = elm_glview_add(win);
 
elm_glview_mode_set(gl, ELM_GLVIEW_DEPTH | ELM_GLVIEW_DIRECT | ELM_GLVIEW_CLIENT_SIDE_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.

EvasGL

Evas_GL_Config *cfg;
cfg = evas_gl_config_new();
 
cfg->options_bits = EVAS_GL_OPTIONS_DIRECT | EVAS_GL_OPTIONS_CLIENT_SIDE_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.

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() in order to properly handle the current rotation of the view. It will always return 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