Glview example with C++ Binding

In this example we'll illustrate how to use Glview and it's features.

The first part consists of including the headers. In this case we need to include Elementary.hh, Evas_GL.h and stdio.h.

#include <Elementary.hh>
#include <Evas_GL.h>
#include <stdio.h>

Continuing with the code, at this point we create a GL related struct:

typedef struct _GLData GLData;
struct _GLData
{
Evas_GL_API *glapi;
GLuint program;
GLuint vtx_shader;
GLuint fgmt_shader;
GLuint vbo;
int initialized : 1;
};

Here we're simply initializing a type float, that we named red.

static float red = 1.0;

In this example we'll need a type C helper function to load shaders from a shader source.

static GLuint
load_shader(GLData *gld, GLenum type, const char *shader_src )
{
Evas_GL_API *gl = gld->glapi;
GLuint shader;
GLint compiled;

Inside this function we create the shader objectand load/compile shader source.

shader = gl->glCreateShader(type);
if (shader==0)
return 0;
gl->glShaderSource(shader, 1, &shader_src, NULL);
gl->glCompileShader(shader);
gl->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled)
{
GLint info_len = 0;
gl->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
if (info_len > 1)
{
char* info_log = new char[sizeof(char) * info_len];
gl->glGetShaderInfoLog(shader, info_len, NULL, info_log);
printf("Error compiling shader:\n%s\n======\n%s\n======\n", info_log, shader_src );
delete [] info_log;
}
gl->glDeleteShader(shader);
return 0;
}
return shader;

Completing our load shader function.

}

This example will also need a function to initialize the shader and program object.

static int
init_shaders(GLData *gld)
{
Evas_GL_API *gl = gld->glapi;
GLbyte vShaderStr[] =
"attribute vec4 vPosition; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
"} \n";
GLbyte fShaderStr[] =
"#ifdef GL_ES \n"
"precision mediump float; \n"
"#endif \n"
"void main() \n"
"{ \n"
" gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );\n"
"} \n";
GLint linked;

In this function we load the vertex/fragment shaders, create the program object and finish our funtion.

gld->vtx_shader = load_shader(gld, GL_VERTEX_SHADER, (const char*)vShaderStr);
gld->fgmt_shader = load_shader(gld, GL_FRAGMENT_SHADER, (const char*)fShaderStr);
gld->program = gl->glCreateProgram( );
if (gld->program==0)
return 0;
gl->glAttachShader(gld->program, gld->vtx_shader);
gl->glAttachShader(gld->program, gld->fgmt_shader);
gl->glBindAttribLocation(gld->program, 0, "vPosition");
gl->glLinkProgram(gld->program);
gl->glGetProgramiv(gld->program, GL_LINK_STATUS, &linked);
if (!linked)
{
GLint info_len = 0;
gl->glGetProgramiv(gld->program, GL_INFO_LOG_LENGTH, &info_len);
if (info_len > 1)
{
char* info_log = new char[sizeof(char) * info_len];
gl->glGetProgramInfoLog(gld->program, info_len, NULL, info_log);
printf("Error linking program:\n%s\n", info_log);
delete [] info_log;
}
gl->glDeleteProgram(gld->program);
return 0;
}
return 1;
}

We need the following callbacks:

static void
_init_gl(Evas_Object *obj)
{
GLData *gld = static_cast<GLData*>(evas_object_data_get(obj, "gld"));
assert(gld != 0);
::elm::glview glv(eo_ref(obj));
Evas_GL_API *gl = glv.gl_api_get();
GLfloat vVertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f };
if (!init_shaders(gld))
{
std::cout << "Error Initializing Shaders" << std::endl;
return;
}
gl->glGenBuffers(1, &gld->vbo);
gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vbo);
gl->glBufferData(GL_ARRAY_BUFFER, 3 * 3 * 4, vVertices, GL_STATIC_DRAW);
}

static void
_del_gl(Evas_Object *obj)
{
GLData *gld = static_cast<GLData*>(evas_object_data_get(obj, "gld"));
if (!gld)
{
std::cout << "Unable to get GLData. " << std::endl;
return;
}
::elm::glview glv(eo_ref(obj));
Evas_GL_API *gl = glv.gl_api_get();
gl->glDeleteShader(gld->vtx_shader);
gl->glDeleteShader(gld->fgmt_shader);
gl->glDeleteProgram(gld->program);
gl->glDeleteBuffers(1, &gld->vbo);
evas_object_data_del(obj, "..gld");
free(gld);
}

}

static void
_draw_gl(Evas_Object *obj)
{
GLData *gld = static_cast<GLData*>(evas_object_data_get(obj, "gld"));
::elm::glview glv(eo_ref(obj));
Evas_GL_API *gl = glv.gl_api_get();
if (!gld) return;
int w, h;
glv.size_get(&w, &h);
gl->glViewport(0, 0, w, h);
gl->glClearColor(red,0.8,0.3,1);
gl->glClear(GL_COLOR_BUFFER_BIT);

Inside this callback, we'll draw a triangle.

gl->glEnable(GL_BLEND);
gl->glUseProgram(gld->program);
gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vbo);
gl->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
gl->glEnableVertexAttribArray(0);
gl->glDrawArrays(GL_TRIANGLES, 0, 3);

Still inside as an option we are going to flush the GL pipeline and end our callback.

gl->glFinish();
red -= 0.1;
if (red < 0.0) red = 1.0;
}

We create _anim to notify that glview has changed so it can render.

static Eina_Bool
_anim(void* data)
{
static_cast<elm::glview*>(data)->changed_set();
return EINA_TRUE;
}

Now that we finished with the GL preparations, we'll start the main code and initialize our GLData pointer object to NULL and run a check just in case.

EAPI_MAIN int
elm_main (int argc, char *argv[])
{
GLData *gld = NULL;
if (!(gld = static_cast<GLData*>(calloc(1, sizeof(GLData))))) return 1;

Let's set the elm_policy, which defines for a given policy group/identifier a new policy's value, respectively. In this example the only policy we need to set a value for is ELM_POLICY_QUIT, possibles values for it are:

elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_HIDDEN);

As you can see, the policy we chose was to quit when the last win is hidden as opose to examples with the C bindings where we perpetually set it to quit when last win was closed. This changed was necessary because in C++ binding as the elm mainloop stop running all object are destroyed, references are unreferenced and events are stopped at ELM_MAIN().

See also
For more details consult elm_policy_set

Next step is creating an elementary window, in this example we use the C++ binding method with the elm_win_util_standard_add that is a elm_win_legacy function, better explained below. And then we set the autohide state for it.

elm_win_util_standard_add (const char *name, const char *tittle) Adds a window object with standard setup. Parameters:

This creates a window but also puts in a standard background with elm_bg_add(), as well as setting the window title to title. The window type created is of type ELM_WIN_BASIC, with the NULL as the parent widget. Returns the created object or NULL on failure.

The autohide works similarly to autodel, automatically handling "delete,request" signals when set to true, with the difference that it will hide the window, instead of destroying it.

It is specially designed to work together with ELM_POLICY_QUIT_LAST_WINDOW_HIDDEN which allows exiting Elementary's main loop when all the windows are hidden.

::elm::win win(elm_win_util_standard_add("glview simple", "GLView Simple"));
win.autohide_set(true);

Note
autodel and autohide are not mutually exclusive. The window will be destructed if both autodel and autohide is set to EINA_TRUE or true.

Now let's create a box with the C++ binding method, passing our window object as parent, we'll use this box to contain our glview object.

::elm::box bx(efl::eo::parent = win);

To better understand, the function size_hint_weight_set for C++ bindings originated from C bindings function evas_object_size_hint_weight_set, that is EFL Evas type function. With this function we set the hints for an object's weight. The parameters are:

This is not a size enforcement in any way, it's just a hint that should be used whenever appropriate. This is a hint on how a container object should resize a given child within its area.

Containers may adhere to the simpler logic of just expanding the child object's dimensions to fit its own (see the EVAS_HINT_EXPAND helper weight macro in the EFL Evas Documentation) or the complete one of taking each child's weight hint as real weights to how much of its size to allocate for them in each axis. A container is supposed to, after normalizing the weights of its children (with weight hints), distribute the space it has to layout them by those factors – most weighted children get larger in this process than the least ones.

bx.size_hint_weight_set(EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);

Note
Default weight hint values are 0.0, for both axis.

Then we add the box as a resize-object to win informing that when the size of the win changes so should the box's size. Remember always to set the box visibility to true.

In this part we'll create a new elm glview, using the C++ method, in this case it requires that we set Evas_GL_Context_Version with the version_constructor. Evas_GL_Context_Version is a enumeration that defines the available OpenGL ES version numbers, it can be used to create OpenGL-ES 1.1 contexts.

The function size_hint_weight_set works with glview the same way as with box, for more, search above.

The function size_hint_align_set for C++ bindings originated from C bindings function evas_object_size_hint_align_set, that is EFL Evas type function. With this function we set the hints for an object's alignment. The parameters are:

These are hints on how to align an object inside the boundaries of a container/manager. Accepted values are in the 0.0 to 1.0 range, with the special value EVAS_HINT_FILL used to specify "justify" or "fill" by some users. In this case, maximum size hints should be enforced with higher priority, if they are set. Also, any padding hint set on objects should add up to the alignment space on the final scene composition.

For the horizontal component, 0.0 means to the left, 1.0 means to the right. Analogously, for the vertical component, 0.0 to the top, 1.0 means to the bottom.

This is not a size enforcement in any way, it's just a hint that should be used whenever appropriate.

Note
Default alignment hint values are 0.5, for both axis.

Mode is simply for supporting alpha, depth buffering and stencil buffering.

Resize policy tells glview what to do with the surface when it resizes. ELM_VIEW_RESIZE_POLICY_RECREATE will tell it to destroy the current surface and recreate it to the new size.

Render policy tells glview how it would like glview to render gl code. ELM_GLVIEW_RENDER_POLICY_ON_DEMAND will have the gl calls called in the pixel_get callback, which only gets called if the object is visible, hence ON_DEMAND. ALWAYS mode renders it despite the visibility of the object.

Now we'll register our callbacks.

When using the elm box the packing method of the subobj - glview in this case - should be defined. There are four possible methods:

In this and most examples we use pack_end by choice and practicality, in this part of the code we also make glview visible and set to focus.

For a simple demonstration of the animation we'll have to use ecore::animator. As long as tou trigger an update on the image via changed_set() it will be updated.

If you delete gl, this animator will keep running trying to access gl so it's better to delete this animator with ecore_animator_del(), as seen inside the lambda function.

Note
To learn more about Lambda Function and its use in Elementary consult Lambda Functions with Elementary - C++11.

We're going to add a "OK" button to end the program. First step is to create it using the C++ method, setting it's parent.

Second, set the text, alignment and weight hints, the hints work the same as with box and glview.

Pack our button in the same box as glview and set the visibility for it.

As a final step for our button, we are going to add a clicked callback, using again Lambda Type Function.

Note
To learn more about Lambda Function and its use in Elementary consult Lambda Functions with Elementary - C++11.

Now we only have to set the size for our window and make it visible.

And finally, start the elm mainloop, starting to handle events and drawing operations.

See full code for this example here .