OpenGL ES
The OpenGL ES overview shows the interaction among Graphics subsystems, OpenGL ES, and EGL defined by the Khronos Group.
OpenGL ES is a standard specification defining a cross-language, cross-platform OpenGL ES API for writing applications that produce 2D and 3D computer graphics. OpenGL ES 1.1 and 2.0 are supported in Tizen 2.3. (OpenGL ES 3.0 will be supported in the next Tizen version.)
EGL is an adhesive layer between OpenGL ES and the underlying native platform window system. EGL communicates with the Window system to get information on the application window, creates the drawing surface, and manages rendering context and resources.
Figure: OpenGL ES structure
OpenGL ES in Tizen
Building an OpenGL ES application in Tizen requires knowledge about designing UI applications with EFL.
Tizen native applications do not depend on the Window system, because the Window system is an internal Tizen module, which can be enhanced or replaced. EFL provides a method that draws the OpenGL ES content and encapsulates the EGL and the native Window system.
Figure: OpenGL ES and EFL
A GLView creates a drawable GL surface for the application, and sets up all the required callbacks. The application can use GLView with various UI components, such as toolbar or button.
GLView internally uses EvasGL. It is an abstraction layer on top of EGL, which provides the necessary features for most applications in a platform-independent way. Since the goal of EvasGL is to abstract the underlying platform, only a subset of the features can be used by applications.
Using GLView is recommended for usual OpenGL ES programs, such as 3D game applications. But if you need pbuffer surface or extension functions, you have to understand EvasGL.
EvasGL vs. GLView
While it is possible to create an OpenGL ES application by just using EvasGL, it could be difficult to comprehend due to the low-level nature of these APIs. However, it enables detailed operations.
GLView Programming Guide
Setting up an OpenGL ES Surface
The easiest way to use OpenGL ES within a Tizen application is to rely on the GLView component.
GLView is a simple Elementary UI component that sets up an OpenGL ES target surface and a context, and allows you to embed it in any Tizen application. GLView is basically a wrapper around EvasGL, the OpenGL ES/EGL abstraction layer of EFL.
Creating a Basic Application
Declare the global variable using ELEMENTARY_GLVIEW_GLOBAL_DEFINE(), create a GLView object. and use ELEMENTARY_GLVIEW_GLOBAL_USE(glview). These macros help you to call GL functions directly.
Now, you can call GL functions. For more detailed information, see Elementary_GL_Helpers.h file.
#include <app.h> #include <Elementary_GL_Helpers.h> // This code is to be placed at the beginning of any function using GLES 2.0 APIs // When using this macro, you can call all glFunctions without changing their code // For details, see Elementary_GL_Helpers.h ELEMENTARY_GLVIEW_GLOBAL_DEFINE()
- Manage HW acceleration.
To develop a GL application, call the elm_config_accel_preference_set() function before creating a window. This makes an application to use GPU. The function is supported since Tizen 2.3.
To use the Direct Rendering mode of EvasGL or EFL WebKit (EWK), set the same option values (depth, stencil, and MSAA) to a rendering engine and a GLView object. The EWK options are depth24 and stencil8. 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 or abnormal rendering occurs. These special options are supported since Tizen 2.3.1.
static bool app_create(void *data) { appdata_s *ad = data; elm_config_accel_preference_set("gl"); ad->win = elm_win_util_standard_add("GLView example", "GLView example"); evas_object_show(ad->win); ad->glview = ad->glview_create(ad->win); // This macro sets the global variable holding the GL API, // so that it is available to the application // Use it right after setting up the GL context object // For details, see Elementary_GL_Helpers.h ELEMENTARY_GLVIEW_GLOBAL_USE(ad->glview); glview_start(ad->glview); return true; }
- Add the OpenGL ES view to the application:
// This is the GL initialization function static Evas_Object *glview_create(Evas_Object *win) { Evas_Object *glview; // This creates the UI component itself glview = elm_glview_add(win); elm_win_resize_object_add(win, glview); // Request a surface with Depth and Stencil support (default buffer sizes) elm_glview_mode_set(glview, ELM_GLVIEW_DEPTH | ELM_GLVIEW_STENCIL); // Set the basic policies to handle the view transparently elm_glview_resize_policy_set(glview, ELM_GLVIEW_RESIZE_POLICY_RECREATE); elm_glview_render_policy_set(glview, ELM_GLVIEW_RENDER_POLICY_ON_DEMAND); // Set the 4 main callbacks elm_glview_init_func_set(glview, init_gl); elm_glview_del_func_set(glview, del_gl); elm_glview_render_func_set(glview, draw_gl); elm_glview_resize_func_set(glview, resize_gl); // Finally show this view evas_object_size_hint_min_set(glview, 250, 250); evas_object_show(glview); return glview; }
Setting up the Callbacks
To set up callbacks:
- Callback for initialization
The initialization callback is called when the GLView is 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.
// GL Init function static void init_gl(Evas_Object *glview) { glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Do any form of OpenGL ES initialization here // init_shaders(); // init_vertices(); }
- 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. Because the GLView size can be changed by a parent container, you must set a resize callback and reset the viewport size with the new GLView size.
// GLView resize function static void resize_gl(Evas_Object *glview) { int w, h; elm_glview_size_get(glview, &w, &h); glViewport(0, 0, w, h); }
- 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.
// GL draw callback static void draw_gl(Evas_Object *glview) { // Paint it blue glClearColor(0.2, 0.2, 0.6, 1.0); glClear(GL_COLOR_BUFFER_BIT); // The usual OpenGL ES draw commands come here // draw_scene(); }
- 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.
// Delete GLView callback static void del_gl(Evas_Object *glview) { // Destroy all the OpenGL ES resources here // destroy_shaders(); // destroy_objects(); }
- Add an animator.
The application above is technically working but the scene does not get updated unless the frame is marked as such. Games sometimes want to use an animator to have a regular update of the scene. Here is an example for a default update refresh rate:
static Eina_Bool anim_cb(void *data) { Evas_Object *glview = data; elm_glview_changed_set(glview); return ECORE_CALLBACK_RENEW; } static void glview_start(Evas_Object *glview) { ecore_animator_add(anim_cb, glview); }
Any other event can be used to refresh the view, for example user input if the view need to be updated.
Note |
---|
Since the Evas rendering engine uses its own GL context internally, the application has to call the gl functions inside the 4 GLView callback functions (initialization, resizing, drawing, deleting) to be guaranteed rendering correctness. |
While GLView is an abstraction above EvasGL, it is possible to use EvasGL directly for more low-level and advanced features, such as:
- Creating new contexts
- Creating new surfaces
- Creating PBuffer surfaces
- Calling extensions
For all those reasons, a direct access to the EvasGL object is required. When you use GLView, you can use the following code:
Evas_GL *evgl = elm_glview_evas_gl_get(glview); // Then it is possible to call any evas_gl function with it
Note |
---|
Do not destroy the EvasGL object. Its life-cycle is defined by the GLView object. |
EvasGL Programming Guide
This guide assumes that the application uses EvasGL directly instead of using the GLView. (If the application uses a GLView, EvasGL is created internally.)
First, you can declare the global variable using the EVAS_GL_GLOBAL_GLES2_DEFINE() macro. Then, create an EvasGL and use EVAS_GL_GLOBAL_GLES2_USE(evasgl, evasgl context). This is similar to the GLView macro. Both macros help you to call GL functions directly.
Now, you can call GL functions. For more detailed information, see the Evas_GL_GLES2_Helpers.h file.
#include <app.h> #include <Evas_GL_GLES2_Helpers.h> // This code is to be placed at the beginning of any function using GLES 2.0 APIs // When using this macro, you can call all glFunctions without changing their code // For details, see Evas_GL_GLES2_Helpers.h EVAS_GL_GLOBAL_GLES2_DEFINE();
Declaration of EvasGL Objects
This is how to define the application data structure to hold all the objects for your EvasGL application:
typedef struct appdata { Evas_Object *win; Evas_Object *img; Evas_GL *evasgl; Evas_GL_API *glapi; Evas_GL_Context *ctx; Evas_GL_Surface *sfc; Evas_GL_Config *cfg; Evas_Coord sfc_w; Evas_Coord sfc_h; unsigned int program; unsigned int vtx_shader; unsigned int fgmt_shader; unsigned int vbo; } appdata_s;
- Evas_Object *win: Application window.
- Evas_Object *img: OpenGL ES canvas.
- Evas_GL *evasgl: EvasGL Object for rendering gl in Evas.
- Evas_GL_API *glapi: EvasGL API object that contains the GL APIs to be used in Evas GL.
- Evas_GL_Context *ctx: EvasGL Context object, a GL rendering context in Evas GL.
- Evas_GL_Surface *sfc: EvasGL Surface object, a GL rendering target in Evas GL.
- Evas_GL_Config *cfg: EvasGL Surface configuration object for surface creation.
Creating the Elm Window and EvasGL
To create the Elm window and EvasGL:
- Manage HW acceleration.
To develop a GL application, call the elm_config_accel_preference_set() function before creating a window. THis makes an application to use GPU. The function is supported since Tizen 2.3.
To use the Direct Rendering mode of EvasGL, set the same option values (depth, stencil, and MSAA) to a rendering engine and a Evas_GL_Surface object. You can set the option values to a rendering engine using the elm_config_accel_preference_set() function and to a Evas_GL_Surface object using the Evas_GL_Config object. If the Evas_GL_Config object option values are bigger or higher than the rendering engine's, the Direct Rendering mode is disabled or abnormal rendering occurs. These special options are supported since Tizen 2.3.1.
Evas_Object *win; // To use OpenGL ES, the application must switch on hardware acceleration // To enable that, call elm_config_accel_preference_set() with "opengl" // before creating the Elm window // This function is supported since 2.3. elm_config_accel_preference_set("opengl"); // Creating Elm window ad->win = elm_win_util_standard_add("Evas_GL Example", "Evas_GL Example");
You can create your EvasGL handler using the evas_gl_new(Evas * e) function. This initializer takes as a parameter the Evas canvas on which OpenGL ES is to be used. When developing an application with Elementary, use the canvas of your window:
ad->evasgl = evas_gl_new(evas_object_evas_get(ad->win));
To free the memory allocated to this handler, use the evas_gl_free(Evas_GL *evas_gl) function.
- Create a surface.
You must allocate a new config object to fill the surface out using the evas_gl_config_new() function. As long as Evas creates a config object for the user, it takes care of the backward compatibility issue. Once you have your config object, you can specify the surface settings:
appdata_s *ad; ad->cfg = evas_gl_config_new(); ad->cfg->color_format = EVAS_GL_RGBA_8888; // Surface Color Format ad->cfg->depth_bits = EVAS_GL_DEPTH_BIT_24; // Surface Depth Format ad->cfg->stencil_bits = EVAS_GL_STENCIL_NONE; // Surface Stencil Format ad->cfg->options_bits = EVAS_GL_OPTIONS_NONE; // Configuration options (here, no extra options)
Once we have configured the surface behavior, we must initialize the surface using evas_gl_surface_create(Evas_GL* evas_gl, Evas_GL_Config * cfg, int w, int h). This function takes the given Evas_GL object as the first parameter and the pixel format, and configuration of the rendering surface as the second parameter. The last two parameters are the width and height of the surface, which we recover directly from the window.
Evas_Coord w, h; evas_object_geometry_get(ad->win, NULL, NULL, &w, &h); ad->sfc = evas_gl_surface_create(ad->evasgl, ad->cfg, w, h);
To manually delete a GL surface, use the evas_gl_surface_destroy(Evas_GL *evas_gl, Evas_GL_Surface *surf) function.
- Create a context.
Create a context for Evas_GL using the evas_gl_context_create(Evas_GL * evas_gl, Evas_GL_Context * share_ctx) function. You can merge the context with a higher context definition you must pass as a second parameter.
ad->ctx = evas_gl_context_create(ad->evasgl, NULL); // This macro sets the global variable holding the GL API, // so that it is available to the application // Use it right after setting up the GL context object // For details, see Evas_GL_GLES2_Helpers.h EVAS_GL_GLOBAL_GLES2_USE(ad->evasgl, ad->ctx);
To delete the context later, use the evas_gl_context_destroy(Evas_GL *evas_gl, Evas_GL_Context *ctx) function. To delete the entire configuration object, use the evas_gl_config_free(Evas_GL_Config *cfg) function instead.
Getting OpenGL ES APIs
If you want to get the API of OpenGL ES, you can get the API for rendering OpenGL ES with the evas_gl_api_get(Evas_GL *evas_gl_)function. This function returns a structure that contains all the OpenGL ES functions you can use to render in Evas. These functions consist of all the standard OpenGL ES 2.0 functions and any extra ones Evas has decided to provide in addition. If you have your code ported to OpenGL ES 2.0, it is easy to render to Evas. (OpenGL ES 3.0 is supported in the next Tizen version.)
If you already use a global macro, such as EVAS_GL_GLOBAL_GLES2_XXX, you need not get the APIs.
ad->glapi = evas_gl_api_get(ad->evasgl);
Callbacks
Now that we have configured the EvasGL environment, we declare a UI component in which all the OpenGL ES transformation takes place. In the example below, we selected the image component because it provides callbacks that allow us to play with the mouse events and coordinates, and we set up an image object that inherits the size of the parent window.
ad->img = evas_object_image_filled_add(evas_object_evas_get(ad->win));
We define the "OpenGL ES main loop" function that is called every time the program attempts to have pixels from the image. We put all the OpenGL ES statements in charge of rendering the scene in this callback.
evas_object_image_pixels_get_callback_set(ad->img, img_pixels_get_cb, ad);
To define a function that takes care of the drawing using EvasGL (called the OpenGL ES main loop), use:
static void img_pixels_get_cb(void *data, Evas_Object *obj) { appdata_s *ad = data; Evas_GL_API *gl = ad->glapi; // Rendering process evas_gl_make_current(ad->evasgl, ad->sfc, ad->ctx); // Because the surface size can be changed, set the viewport in this callback gl->glViewport(0, 0, ad->sfc_w, ad->sfc_h); // Paint it blue gl->glClearColor(0.2, 0.2, 0.6, 1.0); gl->glClear(GL_COLOR_BUFFER_BIT); // The usual OpenGL ES draw commands come here // draw_scene(); }
At every tick, we must set the given context as a current context for the given surface using evas_gl_make_current(Evas_GL *evas_gl, Evas_GL_Surface *surf, Evas_GL_Context *ctx).
You can use the Ecore_Animator to define the OpenGL ES main loop. To do so, create a callback that is called on every animation tick. This animation callback is used only to mark the image as "dirty", meaning that it needs an update next time Evas renders. It calls the pixel get callback that redraws the scene.
static Eina_Bool animate_cb(void *data) { Evas_Object *img = data; evas_object_image_pixels_dirty_set(img, EINA_TRUE); return ECORE_CALLBACK_RENEW; } ecore_animator_add(animate_cb, ad->img);
You can define several other callbacks that have an impact on the drawing depending on the mouse, resize, and deletion events.
evas_object_event_callback_add(ad->img, EVAS_CALLBACK_DEL, img_del_cb, ad); evas_object_event_callback_add(ad->img, EVAS_CALLBACK_MOUSE_DOWN, mouse_down_cb, ad); evas_object_event_callback_add(ad->img, EVAS_CALLBACK_MOUSE_UP, mouse_up_cb, ad); evas_object_event_callback_add(ad->img, EVAS_CALLBACK_MOUSE_MOVE, mouse_move_cb, ad); evas_object_event_callback_add(ad->win, EVAS_CALLBACK_RESIZE, win_resize_cb, ad);
Because the window size can be changed, you must set a resize callback for the window. In addition, you must recreate an Evas_GL_Surface in the resize callback and reset the viewport size with the new window size:
static void win_resize_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) { appdata_s *ad = data; if (ad->sfc) { evas_object_image_native_surface_set(ad->img, NULL); evas_gl_surface_destroy(ad->evasgl, ad->sfc); ad->sfc = NULL; } evas_object_geometry_get(obj, NULL, NULL, &ad->sfc_w, &ad->sfc_h); evas_object_image_size_set(ad->img, ad->sfc_w, ad->sfc_h); evas_object_resize(ad->img, ad->sfc_w, ad->sfc_h); evas_object_show(ad->img); if (!ad->sfc) { Evas_Native_Surface ns; ad->sfc = evas_gl_surface_create(ad->evasgl, ad->cfg, ad->sfc_w, ad->sfc_h); evas_gl_native_surface_get(ad->evasgl, ad->sfc, &ns); evas_object_image_native_surface_set(ad->img, &ns); evas_object_image_pixels_dirty_set(ad->img, EINA_TRUE); } }
Setting a Surface into the Image Object
We can also fill in the native Surface information from the given EvasGL surface. For example, to adapt the surface to the target image when the size of the canvas changes, use the following code.
Evas_Native_Surface ns; evas_gl_native_surface_get(ad->evasgl, ad->sfc, &ns); evas_object_image_native_surface_set(ad->img, &ns);