From ba925bd3e81698bd426313b8e6f3583fff092e69 Mon Sep 17 00:00:00 2001 From: raster Date: Fri, 3 Jun 2011 07:09:25 +0000 Subject: [PATCH] From: "Sung W. Park" Subject: Re: [E-devel] [RFC] ELM GLView Elementary GLview wiedget giving simplified access to do opengl drawing in a widget in elm so you pretty much do no work to set it up and just provide a render function to draw the contents when it needs drawing. Thanks to Sungwoo Park and Samsung git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/elementary@59909 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33 --- AUTHORS | 1 + src/bin/Makefile.am | 3 +- src/bin/test.c | 2 + src/bin/test_glview.c | 588 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib/Elementary.h.in | 39 ++++ src/lib/Makefile.am | 1 + src/lib/elm_glview.c | 493 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1126 insertions(+), 1 deletion(-) create mode 100644 src/bin/test_glview.c create mode 100644 src/lib/elm_glview.c diff --git a/AUTHORS b/AUTHORS index c0bab10..6ca77c2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,3 +39,4 @@ Aharon Hillel Shinwoo Kim Govindaraju SM Prince Kumar Dubey +Sung W. Park diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index da028cc..05d8c00 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -103,7 +103,8 @@ test_weather.c \ test_win_inline.c \ test_win_state.c \ test_grid.c \ -test_flip_page.c +test_flip_page.c \ +test_glview.c if HAVE_EIO elementary_test_SOURCES += test_eio.c diff --git a/src/bin/test.c b/src/bin/test.c index 0c60835..359f3f0 100644 --- a/src/bin/test.c +++ b/src/bin/test.c @@ -133,6 +133,7 @@ void test_segment_control(void *data, Evas_Object *obj, void *event_info); void test_store(void *data, Evas_Object *obj, void *event_info); void test_win_inline(void *data, Evas_Object *obj, void *event_info); void test_grid(void *data, Evas_Object *obj, void *event_info); +void test_glview(void *data, Evas_Object *obj, void *event_info); #ifdef HAVE_EIO void test_eio(void *data, Evas_Object *obj, void *event_info); #endif @@ -404,6 +405,7 @@ my_win_main(char *autorun) ADD_TEST("Store", test_store); ADD_TEST("Window Inline", test_win_inline); ADD_TEST("Grid", test_grid); + ADD_TEST("GLView", test_glview); #undef ADD_TEST if (autorun) diff --git a/src/bin/test_glview.c b/src/bin/test_glview.c new file mode 100644 index 0000000..d8b9290 --- /dev/null +++ b/src/bin/test_glview.c @@ -0,0 +1,588 @@ +#include +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif +#ifndef ELM_LIB_QUICKLAUNCH + +#ifndef M_PI +#define M_PI 3.14159265 +#endif + +typedef struct _Gear { + GLfloat *vertices; + GLuint vbo; + int count; +} Gear; + +// GL related data here.. +typedef struct _GLData +{ + Evas_GL_API *glapi; + GLuint program; + GLuint vtx_shader; + GLuint fgmt_shader; + int initialized : 1; + int mouse_down : 1; + + // Gear Stuff + GLfloat view_rotx; + GLfloat view_roty; + GLfloat view_rotz; + + Gear *gear1; + Gear *gear2; + Gear *gear3; + + GLfloat angle; + + GLuint proj_location; + GLuint light_location; + GLuint color_location; + + GLfloat proj[16]; + GLfloat light[3]; + +} GLData; + + +static void gears_init(GLData *gld); +static void gears_reshape(GLData *gld, int width, int height); +static void render_gears(GLData *gld); + +//--------------------------------// +// Gear Stuff.... +static GLfloat * +vert(GLfloat *p, GLfloat x, GLfloat y, GLfloat z, GLfloat *n) +{ + p[0] = x; + p[1] = y; + p[2] = z; + p[3] = n[0]; + p[4] = n[1]; + p[5] = n[2]; + + return p + 6; +} + +/* Draw a gear wheel. You'll probably want to call this function when + * building a display list since we do a lot of trig here. + * + * Input: inner_radius - radius of hole at center + * outer_radius - radius at center of teeth + * width - width of gear + * teeth - number of teeth + * tooth_depth - depth of tooth + */ +static Gear * +make_gear(GLData *gld, GLfloat inner_radius, GLfloat outer_radius, GLfloat width, + GLint teeth, GLfloat tooth_depth) +{ + GLint i; + GLfloat r0, r1, r2; + GLfloat da; + GLfloat *v; + Gear *gear; + double s[5], c[5]; + GLfloat normal[3]; + const int tris_per_tooth = 20; + Evas_GL_API *gl = gld->glapi; + + gear = (Gear*)malloc(sizeof(Gear)); + if (gear == NULL) + return NULL; + + r0 = inner_radius; + r1 = outer_radius - tooth_depth / 2.0; + r2 = outer_radius + tooth_depth / 2.0; + + da = 2.0 * M_PI / teeth / 4.0; + + gear->vertices = calloc(teeth * tris_per_tooth * 3 * 6, + sizeof *gear->vertices); + s[4] = 0; + c[4] = 1; + v = gear->vertices; + for (i = 0; i < teeth; i++) + { + s[0] = s[4]; + c[0] = c[4]; + s[1] = sin(i * 2.0 * M_PI / teeth + da); + c[1] = cos(i * 2.0 * M_PI / teeth + da); + s[2] = sin(i * 2.0 * M_PI / teeth + da * 2); + c[2] = cos(i * 2.0 * M_PI / teeth + da * 2); + s[3] = sin(i * 2.0 * M_PI / teeth + da * 3); + c[3] = cos(i * 2.0 * M_PI / teeth + da * 3); + s[4] = sin(i * 2.0 * M_PI / teeth + da * 4); + c[4] = cos(i * 2.0 * M_PI / teeth + da * 4); + + normal[0] = 0.0; + normal[1] = 0.0; + normal[2] = 1.0; + + v = vert(v, r2 * c[1], r2 * s[1], width * 0.5, normal); + + v = vert(v, r2 * c[1], r2 * s[1], width * 0.5, normal); + v = vert(v, r2 * c[2], r2 * s[2], width * 0.5, normal); + v = vert(v, r1 * c[0], r1 * s[0], width * 0.5, normal); + v = vert(v, r1 * c[3], r1 * s[3], width * 0.5, normal); + v = vert(v, r0 * c[0], r0 * s[0], width * 0.5, normal); + v = vert(v, r1 * c[4], r1 * s[4], width * 0.5, normal); + v = vert(v, r0 * c[4], r0 * s[4], width * 0.5, normal); + + v = vert(v, r0 * c[4], r0 * s[4], width * 0.5, normal); + v = vert(v, r0 * c[0], r0 * s[0], width * 0.5, normal); + v = vert(v, r0 * c[4], r0 * s[4], -width * 0.5, normal); + v = vert(v, r0 * c[0], r0 * s[0], -width * 0.5, normal); + + normal[0] = 0.0; + normal[1] = 0.0; + normal[2] = -1.0; + + v = vert(v, r0 * c[4], r0 * s[4], -width * 0.5, normal); + + v = vert(v, r0 * c[4], r0 * s[4], -width * 0.5, normal); + v = vert(v, r1 * c[4], r1 * s[4], -width * 0.5, normal); + v = vert(v, r0 * c[0], r0 * s[0], -width * 0.5, normal); + v = vert(v, r1 * c[3], r1 * s[3], -width * 0.5, normal); + v = vert(v, r1 * c[0], r1 * s[0], -width * 0.5, normal); + v = vert(v, r2 * c[2], r2 * s[2], -width * 0.5, normal); + v = vert(v, r2 * c[1], r2 * s[1], -width * 0.5, normal); + + v = vert(v, r1 * c[0], r1 * s[0], width * 0.5, normal); + + v = vert(v, r1 * c[0], r1 * s[0], width * 0.5, normal); + v = vert(v, r1 * c[0], r1 * s[0], -width * 0.5, normal); + v = vert(v, r2 * c[1], r2 * s[1], width * 0.5, normal); + v = vert(v, r2 * c[1], r2 * s[1], -width * 0.5, normal); + v = vert(v, r2 * c[2], r2 * s[2], width * 0.5, normal); + v = vert(v, r2 * c[2], r2 * s[2], -width * 0.5, normal); + v = vert(v, r1 * c[3], r1 * s[3], width * 0.5, normal); + v = vert(v, r1 * c[3], r1 * s[3], -width * 0.5, normal); + v = vert(v, r1 * c[4], r1 * s[4], width * 0.5, normal); + v = vert(v, r1 * c[4], r1 * s[4], -width * 0.5, normal); + + v = vert(v, r1 * c[4], r1 * s[4], -width * 0.5, normal); + } + + gear->count = (v - gear->vertices) / 6; + + gl->glGenBuffers(1, &gear->vbo); + gl->glBindBuffer(GL_ARRAY_BUFFER, gear->vbo); + gl->glBufferData(GL_ARRAY_BUFFER, gear->count * 6 * 4, + gear->vertices, GL_STATIC_DRAW); + + return gear; +} + +static void +multiply(GLfloat *m, const GLfloat *n) +{ + GLfloat tmp[16]; + const GLfloat *row, *column; + div_t d; + int i, j; + + for (i = 0; i < 16; i++) + { + tmp[i] = 0; + d = div(i, 4); + row = n + d.quot * 4; + column = m + d.rem; + for (j = 0; j < 4; j++) + tmp[i] += row[j] * column[j * 4]; + } + memcpy(m, &tmp, sizeof tmp); +} + +static void +rotate(GLfloat *m, GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + double s, c; + + s = sin(angle); + c = cos(angle); + GLfloat r[16] = + { + x * x * (1 - c) + c, y * x * (1 - c) + z * s, x * z * (1 - c) - y * s, 0, + x * y * (1 - c) - z * s, y * y * (1 - c) + c, y * z * (1 - c) + x * s, 0, + x * z * (1 - c) + y * s, y * z * (1 - c) - x * s, z * z * (1 - c) + c, 0, + 0, 0, 0, 1 + }; + + multiply(m, r); +} + +static void +translate(GLfloat *m, GLfloat x, GLfloat y, GLfloat z) +{ + GLfloat t[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 }; + + multiply(m, t); +} + +static void +draw_gear(GLData *gld, Gear *gear, GLfloat *m, + GLfloat x, GLfloat y, GLfloat angle, const GLfloat *color) +{ + Evas_GL_API *gl = gld->glapi; + GLfloat tmp[16]; + + memcpy(tmp, m, sizeof tmp); + translate(tmp, x, y, 0); + rotate(tmp, 2 * M_PI * angle / 360.0, 0, 0, 1); + gl->glUniformMatrix4fv(gld->proj_location, 1, GL_FALSE, tmp); + gl->glUniform3fv(gld->light_location, 1, gld->light); + gl->glUniform4fv(gld->color_location, 1, color); + + gl->glBindBuffer(GL_ARRAY_BUFFER, gear->vbo); + + gl->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, + 6 * sizeof(GLfloat), NULL); + gl->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, + 6 * sizeof(GLfloat), (GLfloat *) 0 + 3); + gl->glEnableVertexAttribArray(0); + gl->glEnableVertexAttribArray(1); + gl->glDrawArrays(GL_TRIANGLE_STRIP, 0, gear->count); +} + +static void +gears_draw(GLData *gld) +{ + Evas_GL_API *gl = gld->glapi; + + static const GLfloat red[4] = { 0.8, 0.1, 0.0, 1.0 }; + static const GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 }; + static const GLfloat blue[4] = { 0.2, 0.2, 1.0, 1.0 }; + GLfloat m[16]; + + gl->glClearColor(0.8, 0.8, 0.1, 0.5); + gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + memcpy(m, gld->proj, sizeof m); + rotate(m, 2 * M_PI * gld->view_rotx / 360.0, 1, 0, 0); + rotate(m, 2 * M_PI * gld->view_roty / 360.0, 0, 1, 0); + rotate(m, 2 * M_PI * gld->view_rotz / 360.0, 0, 0, 1); + + draw_gear(gld, gld->gear1, m, -3.0, -2.0, gld->angle, red); + draw_gear(gld, gld->gear2, m, 3.1, -2.0, -2 * gld->angle - 9.0, green); + draw_gear(gld, gld->gear3, m, -3.1, 4.2, -2 * gld->angle - 25.0, blue); +} + + +static void render_gears(GLData *gld) +{ + gears_draw(gld); + + gld->angle += 2.0; +} + +/* new window size or exposure */ +static void +gears_reshape(GLData *gld, int width, int height) +{ + Evas_GL_API *gl = gld->glapi; + + GLfloat ar, m[16] = { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.1, 0.0, + 0.0, 0.0, 0.0, 1.0, + }; + + if (width < height) + ar = width; + else + ar = height; + + m[0] = 0.1 * ar / width; + m[5] = 0.1 * ar / height; + memcpy(gld->proj, m, sizeof gld->proj); + gl->glViewport(0, 0, (GLint) width, (GLint) height); +} + +static const char vertex_shader[] = + "uniform mat4 proj;\n" + "attribute vec4 position;\n" + "attribute vec4 normal;\n" + "varying vec3 rotated_normal;\n" + "varying vec3 rotated_position;\n" + "vec4 tmp;\n" + "void main()\n" + "{\n" + " gl_Position = proj * position;\n" + " rotated_position = gl_Position.xyz;\n" + " tmp = proj * normal;\n" + " rotated_normal = tmp.xyz;\n" + "}\n"; + + static const char fragment_shader[] = + //"precision mediump float;\n" + "uniform vec4 color;\n" + "uniform vec3 light;\n" + "varying vec3 rotated_normal;\n" + "varying vec3 rotated_position;\n" + "vec3 light_direction;\n" + "vec4 white = vec4(0.3, 0.3, 0.5, 1.0);\n" + "void main()\n" + "{\n" + " light_direction = normalize(light - rotated_position);\n" + " gl_FragColor = color + white * dot(light_direction, rotated_normal);\n" + "}\n"; + +static void +gears_init(GLData *gld) +{ + Evas_GL_API *gl = gld->glapi; + + const char *p; + char msg[512]; + + gl->glEnable(GL_CULL_FACE); + gl->glEnable(GL_DEPTH_TEST); + + p = vertex_shader; + gld->vtx_shader = gl->glCreateShader(GL_VERTEX_SHADER); + gl->glShaderSource(gld->vtx_shader, 1, &p, NULL); + gl->glCompileShader(gld->vtx_shader); + gl->glGetShaderInfoLog(gld->vtx_shader, sizeof msg, NULL, msg); + //printf("vertex shader info: %s\n", msg); + + p = fragment_shader; + gld->fgmt_shader = gl->glCreateShader(GL_FRAGMENT_SHADER); + gl->glShaderSource(gld->fgmt_shader, 1, &p, NULL); + gl->glCompileShader(gld->fgmt_shader); + gl->glGetShaderInfoLog(gld->fgmt_shader, sizeof msg, NULL, msg); + //printf("fragment shader info: %s\n", msg); + + gld->program = gl->glCreateProgram(); + gl->glAttachShader(gld->program, gld->vtx_shader); + gl->glAttachShader(gld->program, gld->fgmt_shader); + gl->glBindAttribLocation(gld->program, 0, "position"); + gl->glBindAttribLocation(gld->program, 1, "normal"); + + gl->glLinkProgram(gld->program); + gl->glGetProgramInfoLog(gld->program, sizeof msg, NULL, msg); + //printf("info: %s\n", msg); + + gl->glUseProgram(gld->program); + gld->proj_location = gl->glGetUniformLocation(gld->program, "proj"); + gld->light_location = gl->glGetUniformLocation(gld->program, "light"); + gld->color_location = gl->glGetUniformLocation(gld->program, "color"); + + /* make the gears */ + gld->gear1 = make_gear(gld, 1.0, 4.0, 1.0, 20, 0.7); + gld->gear2 = make_gear(gld, 0.5, 2.0, 2.0, 10, 0.7); + gld->gear3 = make_gear(gld, 1.3, 2.0, 0.5, 10, 0.7); +} + + +static void +gldata_init(GLData *gld) +{ + gld->initialized = 0; + gld->mouse_down = 0; + + gld->view_rotx = -20.0; + gld->view_roty = -30.0; + gld->view_rotz = 0.0; + gld->angle = 0.0; + + gld->light[0] = 1.0; + gld->light[1] = 1.0; + gld->light[2] = -5.0; +} + + +//-------------------------// + +static void +_draw_gl(Evas_Object *obj) +{ + int w, h; + Evas_GL_API *gl = elm_glview_gl_api_get(obj); + GLData *gld = evas_object_data_get(obj, "gld"); + if (!gld) return; + + elm_glview_size_get(obj, &w, &h); + + if (!gld->initialized) + { + gears_init(gld); + gld->initialized = 1; + } + + // GL Viewport stuff. you can avoid doing this if viewport is all the + // same as last frame if you want + gears_reshape(gld, w,h); + + render_gears(gld); + gl->glFinish(); +} + +static Eina_Bool +_anim(void *data) +{ + elm_glview_changed_set(data); + return EINA_TRUE; +} + +static void +_on_done(void *data, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__) +{ + evas_object_del((Evas_Object*)data); +} + + +static void +_del(void *data, Evas *evas __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__) +{ + GLData *gld = evas_object_data_get(data, "gld"); + if (!gld) + { + printf("Unable to get GLData. \n"); + return; + } + Evas_GL_API *gl = gld->glapi; + + gl->glDeleteShader(gld->vtx_shader); + gl->glDeleteShader(gld->fgmt_shader); + gl->glDeleteProgram(gld->program); + evas_object_data_del((Evas_Object*)data, "..gld"); + free(gld); + + Ecore_Animator *ani = evas_object_data_get(obj, "ani"); + ecore_animator_del(ani); +} + + +static void +_key_down(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info) +{ + Evas_Event_Key_Down *ev; + ev = (Evas_Event_Key_Down *)event_info; + GLData *gld = evas_object_data_get(obj, "gld"); + + if (strcmp(ev->keyname, "Left") == 0) + { + gld->view_roty += 5.0; + return; + } + + if (strcmp(ev->keyname, "Right") == 0) + { + gld->view_roty -= 5.0; + return; + } + + if (strcmp(ev->keyname, "Up") == 0) + { + gld->view_rotx += 5.0; + return; + } + + if (strcmp(ev->keyname, "Down") == 0) + { + gld->view_rotx -= 5.0; + return; + } + if ((strcmp(ev->keyname, "Escape") == 0) || + (strcmp(ev->keyname, "Return") == 0)) + { + _on_done(data, obj, event_info); + return; + } +} + +static void +_mouse_down(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__) +{ + GLData *gld = evas_object_data_get(obj, "gld"); + gld->mouse_down = 1; +} + +static void +_mouse_move(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__) +{ + Evas_Event_Mouse_Move *ev; + ev = (Evas_Event_Mouse_Move *)event_info; + GLData *gld = evas_object_data_get(obj, "gld"); + float dx = 0, dy = 0; + + if (gld->mouse_down) + { + dx = ev->cur.canvas.x - ev->prev.canvas.x; + dy = ev->cur.canvas.y - ev->prev.canvas.y; + + gld->view_roty += -1.0 * dx; + gld->view_rotx += -1.0 * dy; + } +} + +static void +_mouse_up(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__) +{ + GLData *gld = evas_object_data_get(obj, "gld"); + gld->mouse_down = 0; +} + + +void +test_glview(void *data __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__) +{ + Evas_Object *win, *bg, *bx, *bt, *gl; + Ecore_Animator *ani; + GLData *gld = NULL; + + if (!(gld = calloc(1, sizeof(GLData)))) return; + gldata_init(gld); + + win = elm_win_add(NULL, "glview", ELM_WIN_BASIC); + elm_win_title_set(win, "GLView"); + elm_win_autodel_set(win, 1); + + bg = elm_bg_add(win); + elm_win_resize_object_add(win, bg); + evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(bg); + + bx = elm_box_add(win); + evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, bx); + evas_object_show(bx); + + gl = elm_glview_add(win); + evas_object_size_hint_align_set(gl, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(gl, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_glview_mode_set(gl, ELM_GLVIEW_ALPHA | ELM_GLVIEW_DEPTH); +// elm_glview_scale_policy_set(gl, ELM_GLVIEW_RESIZE_POLICY_SCALE); +// elm_glview_size_set(gl, 256, 256); + elm_glview_scale_policy_set(gl, ELM_GLVIEW_RESIZE_POLICY_RECREATE); + elm_glview_render_policy_set(gl, ELM_GLVIEW_RENDER_POLICY_ALWAYS); + elm_glview_render_func_set(gl, _draw_gl); + elm_box_pack_end(bx, gl); + evas_object_show(gl); + + elm_object_focus(gl); + evas_object_event_callback_add(gl, EVAS_CALLBACK_KEY_DOWN, _key_down, gl); + evas_object_event_callback_add(gl, EVAS_CALLBACK_MOUSE_DOWN, _mouse_down, gl); + evas_object_event_callback_add(gl, EVAS_CALLBACK_MOUSE_UP, _mouse_up, gl); + evas_object_event_callback_add(gl, EVAS_CALLBACK_MOUSE_MOVE, _mouse_move, gl); + + ani = ecore_animator_add(_anim, gl); + gld->glapi = elm_glview_gl_api_get(gl); + evas_object_data_set(gl, "ani", ani); + evas_object_data_set(gl, "gld", gld); + evas_object_event_callback_add(gl, EVAS_CALLBACK_DEL, _del, gl); + + bt = elm_button_add(win); + elm_button_label_set(bt, "OK"); + evas_object_size_hint_align_set(bt, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(bt, EVAS_HINT_EXPAND, 0.0); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + evas_object_smart_callback_add(bt, "clicked", _on_done, win); + + evas_object_resize(win, 320, 480); + evas_object_show(win); +} +#endif diff --git a/src/lib/Elementary.h.in b/src/lib/Elementary.h.in index 07cb3ac..0d94816 100644 --- a/src/lib/Elementary.h.in +++ b/src/lib/Elementary.h.in @@ -143,6 +143,7 @@ Pants #include #include #include +#include #include #include #include @@ -778,6 +779,44 @@ extern "C" { * "clicked" - the user clicked the image */ + /* glview */ + typedef void (*Elm_GLView_Func)(Evas_Object *obj); + + typedef enum _Elm_GLView_Mode + { + ELM_GLVIEW_ALPHA = 1, + ELM_GLVIEW_DEPTH = 2, + ELM_GLVIEW_STENCIL = 4 + } Elm_GLView_Mode; + + /** + * Defines a policy for the glview resizing. + * + * @note Default is ELM_GLVIEW_RESIZE_POLICY_RECREATE + */ + typedef enum _Elm_GLView_Resize_Policy + { + ELM_GLVIEW_RESIZE_POLICY_RECREATE = 1, /**< Resize the internal surface along with the image */ + ELM_GLVIEW_RESIZE_POLICY_SCALE = 2 /**< Only reize the internal image and not the surface */ + } Elm_GLView_Resize_Policy; + + typedef enum _Elm_GLView_Render_Policy + { + ELM_GLVIEW_RENDER_POLICY_ON_DEMAND = 1, /**< Render only when there is a need for redrawing */ + ELM_GLVIEW_RENDER_POLICY_ALWAYS = 2 /**< Render always even when it is not visible */ + } Elm_GLView_Render_Policy; + + + EAPI Evas_Object *elm_glview_add(Evas_Object *parent) EINA_ARG_NONNULL(1); + EAPI void elm_glview_size_set(Evas_Object *obj, Evas_Coord width, Evas_Coord height) EINA_ARG_NONNULL(1); + EAPI void elm_glview_size_get(Evas_Object *obj, Evas_Coord *width, Evas_Coord *height) EINA_ARG_NONNULL(1); + EAPI Evas_GL_API *elm_glview_gl_api_get(Evas_Object *obj) EINA_ARG_NONNULL(1); + EAPI Eina_Bool elm_glview_mode_set(Evas_Object *obj, Elm_GLView_Mode mode) EINA_ARG_NONNULL(1); + EAPI Eina_Bool elm_glview_scale_policy_set(Evas_Object *obj, Elm_GLView_Resize_Policy policy) EINA_ARG_NONNULL(1); + EAPI Eina_Bool elm_glview_render_policy_set(Evas_Object *obj, Elm_GLView_Render_Policy policy) EINA_ARG_NONNULL(1); + EAPI void elm_glview_render_func_set(Evas_Object *obj, Elm_GLView_Func func); + EAPI void elm_glview_changed_set(Evas_Object *obj) EINA_ARG_NONNULL(1); + /* box */ typedef struct _Elm_Box_Transition Elm_Box_Transition; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 5d94f3b..4a3d0fb 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -104,6 +104,7 @@ elm_transit.c \ elm_util.c \ elm_widget.c \ elm_win.c \ +elm_glview.c \ els_box.c \ els_box.h \ els_cursor.c \ diff --git a/src/lib/elm_glview.c b/src/lib/elm_glview.c new file mode 100644 index 0000000..8545144 --- /dev/null +++ b/src/lib/elm_glview.c @@ -0,0 +1,493 @@ +#include +#include "elm_priv.h" + +/** + * @defgroup GLView + * + * A simple GLView widget that allows GL rendering. + * + * Signals that you can add callbacks for are: + * + * "clicked" - This is called when a user has clicked the image + */ +typedef struct _Widget_Data Widget_Data; + +struct _Widget_Data +{ + Evas_Object *glview_image; + + Elm_GLView_Mode mode; + Elm_GLView_Resize_Policy scale_policy; + Elm_GLView_Render_Policy render_policy; + + Evas_GL *evasgl; + Evas_GL_Config config; + Evas_GL_Surface *surface; + Evas_GL_Context *context; + + Evas_Coord w, h; + + Elm_GLView_Func render_func; + Ecore_Idle_Enterer *render_idle_enterer; + + Eina_Bool initialized; +}; + +static const char *widtype = NULL; +static void _del_hook(Evas_Object *obj); +static void _on_focus_hook(void *data, Evas_Object *obj); + +static const char SIG_FOCUSED[] = "focused"; +static const char SIG_UNFOCUSED[] = "unfocused"; + +static void +_del_hook(Evas_Object *obj) +{ + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return; + + if (wd->render_idle_enterer) ecore_idle_enterer_del(wd->render_idle_enterer); + + if (wd->surface) evas_gl_surface_destroy(wd->evasgl, wd->surface); + if (wd->context) evas_gl_context_destroy(wd->evasgl, wd->context); + if (wd->evasgl) evas_gl_free(wd->evasgl); + + free(wd); +} + +static void +_on_focus_hook(void *data __UNUSED__, Evas_Object *obj) +{ + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return; + + if (elm_widget_focus_get(obj)) + { + evas_object_focus_set(wd->glview_image, EINA_TRUE); + evas_object_smart_callback_call(obj, SIG_FOCUSED, NULL); + } + else + { + evas_object_focus_set(wd->glview_image, EINA_FALSE); + evas_object_smart_callback_call(obj, SIG_UNFOCUSED, NULL); + } +} + +static void +_glview_update_surface(Evas_Object *obj) +{ + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return; + + if (wd->surface) + { + evas_object_image_native_surface_set(wd->glview_image, NULL); + evas_gl_surface_destroy(wd->evasgl, wd->surface); + wd->surface = NULL; + } + + evas_object_image_size_set(wd->glview_image, wd->w, wd->h); + + if (!wd->surface) + { + Evas_Native_Surface ns; + + wd->surface = evas_gl_surface_create(wd->evasgl, &wd->config, + wd->w, wd->h); + evas_gl_native_surface_get(wd->evasgl, wd->surface, &ns); + evas_object_image_native_surface_set(wd->glview_image, &ns); + elm_glview_changed_set(obj); + } +} + +static void +_glview_resize(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__) +{ + Widget_Data *wd = elm_widget_data_get(data); + Evas_Coord w, h; + + if (!wd) return; + if (wd->scale_policy == ELM_GLVIEW_RESIZE_POLICY_RECREATE) + { + evas_object_geometry_get(wd->glview_image, NULL, NULL, &w, &h); + if ((w == 0) || (h == 0)) + { + w = 64; + h = 64; + } + if ((wd->w == w) && (wd->h == h)) return; + wd->w = w; + wd->h = h; + _glview_update_surface(data); + if (wd->render_func) + { + evas_gl_make_current(wd->evasgl, wd->surface, wd->context); + wd->render_func(data); + } + } +} + + +static Eina_Bool +_render_cb(void *obj) +{ + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return EINA_FALSE; + + // Do a make current + if (!evas_gl_make_current(wd->evasgl, wd->surface, wd->context)) + { + wd->render_idle_enterer = NULL; + return EINA_FALSE; + } + + // Call the render function + if (wd->render_func) wd->render_func(obj); + + // Depending on the policy return true or false + if (wd->render_policy == ELM_GLVIEW_RENDER_POLICY_ON_DEMAND) + return EINA_TRUE; + else if (wd->render_policy == ELM_GLVIEW_RENDER_POLICY_ALWAYS) + { + // Return false so it only runs once + wd->render_idle_enterer = NULL; + return EINA_FALSE; + } + else + { + ERR("Invalid Render Policy.\n"); + wd->render_idle_enterer = NULL; + return EINA_FALSE; + } + return EINA_TRUE; +} + +static void +_set_render_policy_callback(Evas_Object *obj) +{ + Widget_Data *wd = elm_widget_data_get(obj); + + switch (wd->render_policy) + { + case ELM_GLVIEW_RENDER_POLICY_ON_DEMAND: + // Delete idle_enterer if it for some reason is around + if (wd->render_idle_enterer) + { + ecore_idle_enterer_del(wd->render_idle_enterer); + wd->render_idle_enterer = NULL; + } + + // Set pixel getter callback + evas_object_image_pixels_get_callback_set + (wd->glview_image, (Evas_Object_Image_Pixels_Get_Cb)_render_cb, obj); + break; + case ELM_GLVIEW_RENDER_POLICY_ALWAYS: + + // Unset the pixel getter callback if set already + evas_object_image_pixels_get_callback_set(wd->glview_image, NULL, NULL); + break; + default: + ERR("Invalid Render Policy.\n"); + return; + } +} + + + +/** + * Add a new glview to the parent + * + * @param parent The parent object + * @return The new object or NULL if it cannot be created + * + * @ingroup GLView + */ +EAPI Evas_Object * +elm_glview_add(Evas_Object *parent) +{ + Evas_Object *obj; + Evas *e; + Widget_Data *wd; + Evas_GL_Config cfg = { EVAS_GL_RGB_8, + EVAS_GL_DEPTH_NONE, + EVAS_GL_STENCIL_NONE }; + + ELM_WIDGET_STANDARD_SETUP(wd, Widget_Data, parent, e, obj, NULL); + + ELM_SET_WIDTYPE(widtype, "glview"); + elm_widget_type_set(obj, "glview"); + elm_widget_sub_object_add(parent, obj); + elm_widget_on_focus_hook_set(obj, _on_focus_hook, NULL); + elm_widget_data_set(obj, wd); + elm_widget_del_hook_set(obj, _del_hook); + + // Evas_GL + wd->evasgl = evas_gl_new(e); + if (!wd->evasgl) + { + ERR("Failed Creating an Evas GL Object.\n"); + return NULL; + } + + // Create image to render Evas_GL Surface + wd->glview_image = evas_object_image_filled_add(e); + evas_object_image_size_set(wd->glview_image, 1, 1); + evas_object_event_callback_add(wd->glview_image, EVAS_CALLBACK_RESIZE, + _glview_resize, obj); + elm_widget_resize_object_set(obj, wd->glview_image); + evas_object_show(wd->glview_image); + + // Initialize variables + wd->mode = 0; + wd->scale_policy = ELM_GLVIEW_RESIZE_POLICY_RECREATE; + wd->render_policy = ELM_GLVIEW_RENDER_POLICY_ON_DEMAND; + wd->config = cfg; + wd->surface = NULL; + + wd->w = 64; + wd->h = 64; + + wd->render_idle_enterer = NULL; + + // Create Context + if (!wd->context) + { + wd->context = evas_gl_context_create(wd->evasgl, NULL); + if (!wd->context) + { + ERR("Error Creating an Evas_GL Context.\n"); + return NULL; + } + } + + return obj; + +} + +/** + * Gets the gl api struct for gl rendering + * + * @param obj The glview object + * @return The api object or NULL if it cannot be created + * + * @ingroup GLView + */ +EAPI Evas_GL_API * +elm_glview_gl_api_get(Evas_Object *obj) +{ + ELM_CHECK_WIDTYPE(obj, widtype) NULL; + Widget_Data *wd = elm_widget_data_get(obj); + + if (!wd) return NULL; + + return evas_gl_api_get(wd->evasgl); +} + + +/** + * Set the mode of the GLView. Supports Three simple modes. + * + * @param obj The glview object + * @param mode The mode Options OR'ed enabling Alpha, Depth, Stencil. + * @return True if set properly. + * + * @ingroup GLView + */ +EAPI Eina_Bool +elm_glview_mode_set(Evas_Object *obj, Elm_GLView_Mode mode) +{ + ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE; + Widget_Data *wd = elm_widget_data_get(obj); + Evas_GL_Config cfg = { EVAS_GL_RGBA_8, + EVAS_GL_DEPTH_NONE, + EVAS_GL_STENCIL_NONE }; + if (!wd) return EINA_FALSE; + + // Set the configs + if (mode & ELM_GLVIEW_ALPHA) + cfg.color_format = EVAS_GL_RGBA_8; + + if (mode & ELM_GLVIEW_DEPTH) + cfg.depth_bits = EVAS_GL_DEPTH_BIT_24; + + if (mode & ELM_GLVIEW_STENCIL) + cfg.stencil_bits = EVAS_GL_STENCIL_BIT_8; + + // Check for Alpha Channel and enable it + if (mode & ELM_GLVIEW_ALPHA) + evas_object_image_alpha_set(wd->glview_image, EINA_TRUE); + else + evas_object_image_alpha_set(wd->glview_image, EINA_FALSE); + + wd->mode = mode; + wd->config = cfg; + + elm_glview_changed_set(obj); + + return EINA_TRUE; +} + +/** + * Set the scaling policy for the glview object. + * + * @param obj The glview object. + * @param policy The scaling policy. + * + * By default, the scaling policy is set to ELM_GLVIEW_RESIZE_POLICY_RECREATE. + * When resize is called it destroys the previous surface and recreates the newly + * specified size. If the policy is set to ELM_GLVIEW_RESIZE_POLICY_SCALE, however, + * glview only scales the image object and not the underlying GL Surface. + * + * @ingroup GLView + */ +EAPI Eina_Bool +elm_glview_scale_policy_set(Evas_Object *obj, Elm_GLView_Resize_Policy policy) +{ + ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE; + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return EINA_FALSE; + + if (policy == wd->scale_policy) return EINA_TRUE; + switch (policy) + { + case ELM_GLVIEW_RESIZE_POLICY_RECREATE: + case ELM_GLVIEW_RESIZE_POLICY_SCALE: + wd->scale_policy = policy; + return EINA_TRUE; + default: + ERR("Invalid Scale Policy.\n"); + return EINA_FALSE; + } + _glview_update_surface(obj); + elm_glview_changed_set(obj); +} + +/** + * Set the render policy for the glview object. + * + * @param obj The glview object. + * @param policy The render policy. + * + * By default, the render policy is set to ELM_GLVIEW_RENDER_POLICY_ON_DEMAND. + * This policy is set such that during the render loop, glview is only redrawn + * if it needs to be redrawn. (i.e. When it is visible) If the policy is set + * to ELM_GLVIEWW_RENDER_POLICY_ALWAYS, it redraws regardless of whether it is + * visible/need redrawing or not. + * + * @ingroup GLView + */ +EAPI Eina_Bool +elm_glview_render_policy_set(Evas_Object *obj, Elm_GLView_Render_Policy policy) +{ + ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE; + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return EINA_FALSE; + + if ((policy != ELM_GLVIEW_RENDER_POLICY_ON_DEMAND) && + (policy != ELM_GLVIEW_RENDER_POLICY_ALWAYS)) + { + ERR("Invalid Render Policy.\n"); + return EINA_FALSE; + } + if (wd->render_policy == policy) return EINA_TRUE; + wd->render_policy = policy; + _set_render_policy_callback(obj); + _glview_update_surface(obj); + return EINA_TRUE; +} + +/** + * Sets the size of the glview + * + * @param obj The glview object + * @param width width of the glview object + * @param height height of the glview object + * + * @ingroup GLView + */ +EAPI void +elm_glview_size_set(Evas_Object *obj, int width, int height) +{ + ELM_CHECK_WIDTYPE(obj, widtype); + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return; + + if ((width == wd->w) && (height == wd->h)) return; + wd->w = width; + wd->h = height; + _glview_update_surface(obj); + elm_glview_changed_set(obj); +} + +/** + * Gets the size of the glview. + * + * @param obj The glview object + * @param width width of the glview object + * @param height height of the glview object + * + * Note that this function returns the actual image size of the glview. + * This means that when the scale policy is set to ELM_GLVIEW_RESIZE_POLICY_SCALE, + * it'll return the non-scaled size. + * + * @ingroup GLView + */ +EAPI void +elm_glview_size_get(Evas_Object *obj, int *width, int *height) +{ + ELM_CHECK_WIDTYPE(obj, widtype); + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return; + + if (width) *width = wd->w; + if (height) *height = wd->h; +} + +/** + * Set the render function that runs in the main loop. + * + * @param obj The glview object. + * @param func The render function to be registered. + * + * @ingroup GLView + */ +EAPI void +elm_glview_render_func_set(Evas_Object *obj, Elm_GLView_Func func) +{ + ELM_CHECK_WIDTYPE(obj, widtype); + Widget_Data *wd = elm_widget_data_get(obj); + if (!wd) return; + + wd->render_func = func; + + _set_render_policy_callback(obj); +} + + +/** + * Notifies that there has been changes in the GLView. + * + * @param obj The glview object. + * + * @ingroup GLView + */ +EAPI void +elm_glview_changed_set(Evas_Object *obj) +{ + ELM_CHECK_WIDTYPE(obj, widtype); + Widget_Data *wd = elm_widget_data_get(obj); + + if (!wd) return; + + evas_object_image_pixels_dirty_set(wd->glview_image, EINA_TRUE); + + if (wd->render_policy == ELM_GLVIEW_RENDER_POLICY_ALWAYS) + { + if (!wd->render_idle_enterer) + + wd->render_idle_enterer = ecore_idle_enterer_before_add((Ecore_Task_Cb)_render_cb, obj); + } + +} + +/* vim:set ts=8 sw=3 sts=3 expandtab cino=>5n-2f0^-2{2(0W1st0 :*/ -- 2.7.4