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:
- GLView component
-
Creating a Basic Application
Create a basic application with a multicolored 3D rotating cube using OpenGL ES 2.0 API provided by the GLView library.
-
Creating a Cube
Create and color a cube.
-
Drawing the Cube with GLView
Draw a cube.
-
Rendering the Cube
Render a cube.
-
Animating the Cube
Create an animation.
-
Implementing UI Component Interaction
Create UI components that interact with the rendering.
-
Implementing Effects
Create fancy effects for the screen.
-
Viewing the Entire Cube Source
View the entire source code of the cube example.
-
Creating a Basic Application
-
Using OpenGL ES Extensions
Check whether an extension is available, and call it.
-
Using EvasGL Extensions
Check whether an Evas extension is available, and use it.
-
Using Direct Rendering
Enhance performance through the Direct Rendering option.
-
Using Client-side Rotation
Use client-side rotation when the application is using Direct Rendering.
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:
-
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.
- 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;
-
Create the OpenGL ES canvas:
-
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");
-
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);
- 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().
-
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);
-
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);
- Callback for initialization
- 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
To create and color the cube:
-
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
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.
-
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:
-
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; }
-
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); }
- Create the matrix multiplication function.
- Create the shader:
-
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;" "}";
-
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);
-
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);
-
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");
-
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. 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:
-
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);
-
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);
-
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);
-
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
Animating the Cube
To animate the cube:
-
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); }
-
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;
-
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);
-
-
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 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):
-
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; }
-
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);
-
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:
-
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; }
-
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);
-
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:
- 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"); }
- 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.
- Check for support.
Before using this extension, check whether it is supported.
if (gl->evasglCreateImageForContext && gl->evasglDestroyImage) { // Good... }
- 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.
- 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.
- Destroy an image.
After releasing all the associated resources, such as FBO and textures, release the image object itself.
gl->evasglDestroyImage(image);
- Check for support.
- 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.