#include "evas_gl_core_private.h"

#ifndef _WIN32
# include <dlfcn.h>
#endif

// EVGL GL Format Pair
typedef struct _GL_Format
{
   int    bit;
   GLenum fmt;
} GL_Format;

// Extended struct size based on the 314 functions found in gl31.h
#define EVAS_GL_API_STRUCT_SIZE (sizeof(Evas_GL_API) + 300 * sizeof(void*))
static Evas_GL_API *gles1_funcs = NULL;
static Evas_GL_API *gles2_funcs = NULL;
static Evas_GL_API *gles3_funcs = NULL;

EVGL_Engine *evgl_engine = NULL;
int _evas_gl_log_dom   = -1;
int _evas_gl_log_level = -1;

typedef void           *(*glsym_func_void_ptr) ();
glsym_func_void_ptr glsym_evas_gl_native_context_get = NULL;
glsym_func_void_ptr glsym_evas_gl_engine_data_get = NULL;

static void _surface_cap_print(int error);
static void _surface_context_list_print();
static void _internal_resources_destroy(void *eng_data, EVGL_Resource *rsc);
static void *_egl_image_create(EVGL_Context *context, int target, void *buffer);
static void _egl_image_destroy(void *image);
static int _evgl_direct_renderable(EVGL_Resource *rsc, EVGL_Surface *sfc);

// NOTE: These constants are "hidden", kinda non public API. They should not
// be used unless you know exactly what you are doing.
// These vars replace environment variables.
#define EVAS_GL_OPTIONS_DIRECT_MEMORY_OPTIMIZE 0x1000
#define EVAS_GL_OPTIONS_DIRECT_OVERRIDE        0x2000

extern void (*EXT_FUNC_GLES1(glGenFramebuffersOES)) (GLsizei n, GLuint* framebuffers);
extern void (*EXT_FUNC_GLES1(glBindFramebufferOES)) (GLenum target, GLuint framebuffer);
extern void (*EXT_FUNC_GLES1(glFramebufferTexture2DOES)) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
extern void (*EXT_FUNC_GLES1(glFramebufferRenderbufferOES)) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
extern GLenum (*EXT_FUNC_GLES1(glCheckFramebufferStatusOES)) (GLenum target);

//---------------------------------------------------------------//
// Internal Resources:
//  - Surface and Context used for internal buffer creation
//---------------------------------------------------------------//
static void *
_internal_resources_create(void *eng_data)
{
   EVGL_Resource *rsc = NULL;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("EVGL Engine not initialized!");
        return NULL;
     }

   // Allocate resource
   rsc = calloc(1, sizeof(EVGL_Resource));
   if (!rsc)
     {
        ERR("Error allocating EVGL_Resource");
        return NULL;
     }
   rsc->id = eina_thread_self();
   rsc->error_state = EVAS_GL_SUCCESS;

   // Get display
   rsc->display = evgl_engine->funcs->display_get(eng_data);
   if (!rsc->display)
     {
        ERR("Error getting display");
        goto error;
     }

   return rsc;

error:
   _internal_resources_destroy(eng_data, rsc);
   return NULL;
}

static void
_internal_resources_destroy(void *eng_data, EVGL_Resource *rsc)
{
   if (!rsc) return;

   if (!eng_data)
     {
        free(rsc);
        return;
     }

   if (rsc->context)
     evgl_engine->funcs->context_destroy(eng_data, rsc->context);
   if (rsc->surface)
     evgl_engine->funcs->surface_destroy(eng_data, rsc->surface);
   if (rsc->window)
     evgl_engine->funcs->native_window_destroy(eng_data, rsc->window);

   free(rsc);
}

static int
_internal_resource_make_current(void *eng_data, EVGL_Surface *sfc, EVGL_Context *ctx)
{
   EVGL_Resource *rsc = NULL;
   void *surface = NULL;
   void *context = NULL;
   int ret = 0;

   // Retrieve the resource object
   if (!(rsc = _evgl_tls_resource_get()))
     {
        if (!(rsc = _evgl_tls_resource_create(eng_data)))
          {
             ERR("Error creating resources in tls.");
             return 0;
          }
     }

   // Set context from input or from resource
   if (ctx)
     context = ctx->context;
   else
     {
        if (!rsc->context)
          {
             // Create a resource context
             rsc->context = evgl_engine->funcs->context_create(eng_data, NULL, EVAS_GL_GLES_2_X);
             if (!rsc->context)
               {
                  ERR("Internal resource context creation failed.");
                  return 0;
               }
          }

        context = (void*)rsc->context;
     }

   if (sfc)
     {
        if (_evgl_direct_renderable(rsc, sfc)) // Direct rendering
          {
             // Do Nothing
          }
        else if ((ctx) && (ctx->pixmap_image_supported)) // Pixmap surface
          {
             if (!sfc->indirect_sfc)
               {
                  evgl_engine->funcs->indirect_surface_create(evgl_engine, eng_data, sfc, sfc->cfg, sfc->w, sfc->h);
                  if (sfc->egl_image) _egl_image_destroy(sfc->egl_image);
                  sfc->egl_image = _egl_image_create(NULL, EVAS_GL_NATIVE_PIXMAP, sfc->indirect_sfc_native);
               }
             surface = (void*)sfc->indirect_sfc;

             if (!ctx->indirect_context)
               ctx->indirect_context = evgl_engine->funcs->gles_context_create(eng_data, ctx, sfc);
             context = (void*)ctx->indirect_context;
          }
        else if (sfc->pbuffer.native_surface) // Pbuffer surface
          {
             surface = (void*)sfc->pbuffer.native_surface;
          }
        else // FBO
          {
             // Do Nothing
          }
     }

   if (!surface)
     {
        // Set the surface to evas surface if it's there
        if (rsc->id == evgl_engine->main_tid)
           rsc->direct.surface = evgl_engine->funcs->evas_surface_get(eng_data);

        if (rsc->direct.surface)
           surface = (void*)rsc->direct.surface;
        else
          {
             if (!rsc->window)
               {
                  // Create resource surface
                  rsc->window = evgl_engine->funcs->native_window_create(eng_data);
                  if (!rsc->window)
                    {
                       ERR("Error creating native window");
                       return 0;
                    }
               }

             if (!rsc->surface)
               {
                  rsc->surface = evgl_engine->funcs->surface_create(eng_data, rsc->window);
                  if (!rsc->surface)
                    {
                       ERR("Error creating native surface");
                       return 0;
                    }
               }

             surface = (void*)rsc->surface;
          }
     }

   // Do the make current
   if (evgl_engine->api_debug_mode)
     DBG("Calling make_current(%p, %p)", surface, context);
   ret = evgl_engine->funcs->make_current(eng_data, surface, context, 1);
   if (!ret)
     {
        ERR("Engine make_current with internal resources failed.");
        return 0;
     }

   return 1;
}

//---------------------------------------------------------------//
// Surface Related Functions
//  - Texture/ Renderbuffer Creation/ Attachment to FBO
//  - Surface capability check
//  - Internal config choose function
//---------------------------------------------------------------//
// Gen Texture
static void
_texture_create(GLuint *tex)
{
   glGenTextures(1, tex);
}

// Create and allocate 2D texture
static void
_texture_allocate_2d(GLuint tex, GLint ifmt, GLenum fmt, GLenum type, int w, int h)
{
   //if (!(*tex))
   //   glGenTextures(1, tex);
   GLint curr_tex = 0;
   glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_tex);

   glBindTexture(GL_TEXTURE_2D, tex);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, ifmt, w, h, 0, fmt, type, NULL);
   glBindTexture(GL_TEXTURE_2D, (GLuint)curr_tex);
}

// Destroy Texture
static void
_texture_destroy(GLuint *tex)
{
   GLint curr_tex = 0;
   glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_tex);

   if ((GLuint)curr_tex == *tex) glBindTexture(GL_TEXTURE_2D, 0);
   if (*tex)
     {
        glDeleteTextures(1, tex);
        *tex = 0;
     }
}

// Attach 2D texture with the given format to already bound FBO
// *NOTE: attach2 here is used for depth_stencil attachment in GLES env.
static void
_texture_attach_2d(GLuint tex, GLenum attach, GLenum attach2, int samples, Evas_GL_Context_Version version)
{
   if (samples && (version == EVAS_GL_GLES_2_X))
     {
#ifdef GL_GLES
        if (EXT_FUNC(glFramebufferTexture2DMultisample))
          {
             EXT_FUNC(glFramebufferTexture2DMultisample)(GL_FRAMEBUFFER,
                                                         attach,
                                                         GL_TEXTURE_2D, tex,
                                                         0, samples);

             if (attach2)
               EXT_FUNC(glFramebufferTexture2DMultisample)(GL_FRAMEBUFFER,
                                                           attach2,
                                                           GL_TEXTURE_2D, tex,
                                                           0, samples);
          }
        else
#endif
        ERR("MSAA not supported.  Should not have come in here...!");
     }
   else if (version == EVAS_GL_GLES_1_X)
     {
        if (EXT_FUNC_GLES1(glFramebufferTexture2DOES))
          EXT_FUNC_GLES1(glFramebufferTexture2DOES)(GL_FRAMEBUFFER, attach, GL_TEXTURE_2D, tex, 0);

        if (attach2)
          if (EXT_FUNC_GLES1(glFramebufferTexture2DOES))
            EXT_FUNC_GLES1(glFramebufferTexture2DOES)(GL_FRAMEBUFFER, attach2, GL_TEXTURE_2D, tex, 0);
     }
   else
     {
        glFramebufferTexture2D(GL_FRAMEBUFFER, attach, GL_TEXTURE_2D, tex, 0);

        if (attach2)
           glFramebufferTexture2D(GL_FRAMEBUFFER, attach2, GL_TEXTURE_2D, tex, 0);

     }
}

static void *
_egl_image_create(EVGL_Context *context, int target, void *buffer)
{
#ifdef GL_GLES
   EGLDisplay dpy = EGL_NO_DISPLAY;
   EGLContext ctx = EGL_NO_CONTEXT;
   EVGL_Resource *rsc = NULL;
   int attribs[10];
   int n = 0;

   // Retrieve the resource object
   if (!(rsc = _evgl_tls_resource_get()))
     {
        ERR("Error creating resources in tls.");
        return NULL;
     }

   dpy = (EGLDisplay)rsc->display;
   if (target == EGL_GL_TEXTURE_2D_KHR)
     {
        ctx = (EGLContext)context->context;
        attribs[n++] = EGL_GL_TEXTURE_LEVEL_KHR;
        attribs[n++] = 0;
     }
   attribs[n++] = EGL_IMAGE_PRESERVED_KHR;
   attribs[n++] = 0;
   attribs[n++] = EGL_NONE;

   return EXT_FUNC_EGL(eglCreateImage)(dpy, ctx, target, (EGLClientBuffer)(uintptr_t)buffer, attribs);
#else
   (void) context; (void) target; (void) buffer;
   return NULL;
#endif
}

static void
_egl_image_destroy(void *image)
{
#ifdef GL_GLES
   EGLDisplay dpy = EGL_NO_DISPLAY;
   EVGL_Resource *rsc = NULL;

   // Retrieve the resource object
   if (!(rsc = _evgl_tls_resource_get()))
     {
        ERR("Error creating resources in tls.");
        return;
     }

   dpy = (EGLDisplay)rsc->display;
   if (!dpy) return;

   EXT_FUNC_EGL(eglDestroyImage)(dpy, image);
#else
   (void) image;
#endif
}

static void
_framebuffer_create(GLuint *buf, Evas_GL_Context_Version version)
{
   if (version == EVAS_GL_GLES_1_X)
     {
        if (EXT_FUNC_GLES1(glGenFramebuffersOES))
            EXT_FUNC_GLES1(glGenFramebuffersOES)(1, buf);
     }
   else
     {
        glGenFramebuffers(1, buf);
     }
}

static void
_framebuffer_bind(GLuint buf, Evas_GL_Context_Version version)
{
   if (version == EVAS_GL_GLES_1_X)
     {
        if (EXT_FUNC_GLES1(glBindFramebufferOES))
          EXT_FUNC_GLES1(glBindFramebufferOES)(GL_FRAMEBUFFER, buf);
     }
   else
     {
        glBindFramebuffer(GL_FRAMEBUFFER, buf);
     }
}

static void
_framebuffer_draw_bind(GLuint buf, Evas_GL_Context_Version version)
{
   if (version == EVAS_GL_GLES_3_X)
     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buf);
}

//This function is not needed in EvasGL backend engine with GLES 2.0.
//But It is useful when EvasGL backend works with GLES 3.X and use read buffers.
static void
_framebuffer_read_bind(GLuint buf, Evas_GL_Context_Version version)
{
   if (version == EVAS_GL_GLES_3_X)
     glBindFramebuffer(GL_READ_FRAMEBUFFER, buf);
}

static GLenum
_framebuffer_check(Evas_GL_Context_Version version)
{
   GLenum ret = 0;
   if (version == EVAS_GL_GLES_1_X)
     {
        if (EXT_FUNC_GLES1(glCheckFramebufferStatusOES))
          ret = EXT_FUNC_GLES1(glCheckFramebufferStatusOES)(GL_FRAMEBUFFER);
     }
   else
     {
        ret = glCheckFramebufferStatus(GL_FRAMEBUFFER);
     }
   return ret;
}

// Gen Renderbuffer
static void
_renderbuffer_create(GLuint *buf)
{
   glGenRenderbuffers(1, buf);
}


// Attach a renderbuffer with the given format to already bound FBO
static void
_renderbuffer_allocate(GLuint buf, GLenum fmt, int w, int h, int samples)
{
   glBindRenderbuffer(GL_RENDERBUFFER, buf);
   if (samples)
     {
        if (glsym_glRenderbufferStorageMultisample)
          glsym_glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt, w, h);
        else
          {
#ifdef GL_GLES
             if (EXT_FUNC(glRenderbufferStorageMultisample))
               EXT_FUNC(glRenderbufferStorageMultisample)(GL_RENDERBUFFER, samples, fmt, w, h);
             else
#endif
               ERR("MSAA not supported.  Should not have come in here...!");
          }
     }
   else
     glRenderbufferStorage(GL_RENDERBUFFER, fmt, w, h);
   glBindRenderbuffer(GL_RENDERBUFFER, 0);
}

static void
_renderbuffer_destroy(GLuint *buf)
{
   if (*buf)
     {
        glDeleteRenderbuffers(1, buf);
        *buf = 0;
     }
}

// Attach a renderbuffer with the given format to already bound FBO
static void
_renderbuffer_attach(GLuint buf, GLenum attach, Evas_GL_Context_Version version)
{
   if (version == EVAS_GL_GLES_1_X)
     {
        if (EXT_FUNC_GLES1(glFramebufferRenderbufferOES))
          EXT_FUNC_GLES1(glFramebufferRenderbufferOES)(GL_FRAMEBUFFER, attach, GL_RENDERBUFFER, buf);
     }
   else
     {
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, attach, GL_RENDERBUFFER, buf);
     }
}

// Check whether the given FBO surface config is supported by the driver
// TODO - we also should test with GLES3's formats.
static int
_fbo_surface_cap_test(GLint color_ifmt, GLenum color_fmt,
                      GLenum depth_fmt, GLenum stencil_fmt, int mult_samples)
{
   GLuint fbo = 0;
   GLuint color_buf = 0;
   GLuint depth_buf = 0;
   GLuint stencil_buf = 0;
   GLuint depth_stencil_buf = 0;
   int depth_stencil = 0;
   int fb_status = 0;
   int w = 2, h = 2;   // Test it with a simple (2,2) surface.  Should I test it with NPOT?
   Evas_GL_Context_Version ver = evas_gl_common_version_check(NULL);

   // Gen FBO
   glGenFramebuffers(1, &fbo);
   glBindFramebuffer(GL_FRAMEBUFFER, fbo);

   // FIXME: GLES 3 support for MSAA is NOT IMPLEMENTED!
   // Needs to use RenderbufferStorageMultisample + FramebufferRenderbuffer

   // Color Buffer Texture
   if ((color_ifmt) && (color_fmt))
     {
        _texture_create(&color_buf);
        _texture_allocate_2d(color_buf, color_ifmt, color_fmt, GL_UNSIGNED_BYTE, w, h);
        _texture_attach_2d(color_buf, GL_COLOR_ATTACHMENT0, 0, mult_samples, ver);
     }

      // Check Depth_Stencil Format First
#ifdef GL_GLES
   if ((depth_fmt == GL_DEPTH_STENCIL_OES) && (!mult_samples))
     {
        _texture_create(&depth_stencil_buf);
        _texture_allocate_2d(depth_stencil_buf, depth_fmt,
                           depth_fmt, GL_UNSIGNED_INT_24_8_OES, w, h);
        _texture_attach_2d(depth_stencil_buf, GL_DEPTH_ATTACHMENT,
                           GL_STENCIL_ATTACHMENT, mult_samples, EINA_FALSE);
        depth_stencil = 1;
     }
   else if ((depth_fmt == GL_DEPTH24_STENCIL8_OES) && (mult_samples))
#else
   if (depth_fmt == GL_DEPTH24_STENCIL8)
#endif
     {
        _renderbuffer_create(&depth_stencil_buf);
        _renderbuffer_allocate(depth_stencil_buf, depth_fmt, w, h, mult_samples);
#ifdef GL_GLES
        _renderbuffer_attach(depth_stencil_buf, GL_DEPTH_ATTACHMENT, EINA_FALSE);
        _renderbuffer_attach(depth_stencil_buf, GL_STENCIL_ATTACHMENT, EINA_FALSE);
#else
        _renderbuffer_attach(depth_stencil_buf, GL_DEPTH_STENCIL_ATTACHMENT, EINA_FALSE);
#endif
        depth_stencil = 1;
     }

   // Depth Attachment
   if ((!depth_stencil) && (depth_fmt))
     {
        _renderbuffer_create(&depth_buf);
        _renderbuffer_allocate(depth_buf, depth_fmt, w, h, mult_samples);
        _renderbuffer_attach(depth_buf, GL_DEPTH_ATTACHMENT, EVAS_GL_GLES_2_X);
     }

   // Stencil Attachment
   if ((!depth_stencil) && (stencil_fmt))
     {
        _renderbuffer_create(&stencil_buf);
        _renderbuffer_allocate(stencil_buf, stencil_fmt, w, h, mult_samples);
        _renderbuffer_attach(stencil_buf, GL_STENCIL_ATTACHMENT, EVAS_GL_GLES_2_X);
     }

   // Check FBO for completeness
   fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

   // Delete Created Resources
   _texture_destroy(&color_buf);
   _renderbuffer_destroy(&depth_buf);
   _renderbuffer_destroy(&stencil_buf);
#ifdef GL_GLES
   _texture_destroy(&depth_stencil_buf);
#else
   _renderbuffer_destroy(&depth_stencil_buf);
#endif

   // Delete FBO
   glBindFramebuffer(GL_FRAMEBUFFER, 0);
   if (fbo) glDeleteFramebuffers(1, &fbo);

   // Return the result
   if (fb_status != GL_FRAMEBUFFER_COMPLETE)
     {
        int err = glGetError();

        if (err != GL_NO_ERROR)
           DBG("glGetError() returns %x ", err);

        return 0;
     }
   else
      return 1;
}

static int
_surface_cap_test(EVGL_Surface_Format *fmt, GL_Format *color,
                  GL_Format *depth, GL_Format *stencil, int samples)
{
   int ret = 0;

   if ( (depth->bit == DEPTH_STENCIL) && (stencil->bit != STENCIL_BIT_8))
      return 0;

   ret = _fbo_surface_cap_test((GLint)color->fmt,
                               color->fmt,
                               depth->fmt,
                               stencil->fmt, samples);
   if (ret)
     {
        fmt->color_bit  = color->bit;
        fmt->color_ifmt = (GLint)color->fmt;
        fmt->color_fmt  = color->fmt;
        fmt->samples = samples;

        if (depth->bit == DEPTH_STENCIL)
          {
             // Depth Stencil Case
             fmt->depth_stencil_fmt = depth->fmt;
             fmt->depth_bit   = DEPTH_BIT_24;
             fmt->depth_fmt   = 0;
             fmt->stencil_bit = STENCIL_BIT_8;
             fmt->stencil_fmt = 0;
          }
        else
          {
             fmt->depth_stencil_fmt = 0;
             fmt->depth_bit = depth->bit;
             fmt->depth_fmt = depth->fmt;
             fmt->stencil_bit = stencil->bit;
             fmt->stencil_fmt = stencil->fmt;
          }
     }

   return ret;
}


static int
_surface_cap_check()
{
   int num_fmts = 0;
   int i, j, k, m;

   GL_Format color[]   = {
                           { COLOR_RGB_888,   GL_RGB },
                           { COLOR_RGBA_8888, GL_RGBA },
                           { -1, -1 },
                         };

#ifdef GL_GLES
   GL_Format depth[]   = {
                           { DEPTH_NONE,   0 },
                           { DEPTH_STENCIL, GL_DEPTH_STENCIL_OES },
                           { DEPTH_STENCIL, GL_DEPTH24_STENCIL8_OES },
                           { DEPTH_BIT_8,   GL_DEPTH_COMPONENT },
                           { DEPTH_BIT_16,  GL_DEPTH_COMPONENT16 },
                           { DEPTH_BIT_24,  GL_DEPTH_COMPONENT24_OES },
                           { DEPTH_BIT_32,  GL_DEPTH_COMPONENT32_OES },
                           { -1, -1 },
                         };
   GL_Format stencil[] = {
                           { STENCIL_NONE, 0 },
                           { STENCIL_BIT_1, GL_STENCIL_INDEX1_OES },
                           { STENCIL_BIT_4, GL_STENCIL_INDEX4_OES },
                           { STENCIL_BIT_8, GL_STENCIL_INDEX8 },
                           { -1, -1 },
                         };
#else
   GL_Format depth[]   = {
                           { DEPTH_NONE,   0 },
                           { DEPTH_STENCIL, GL_DEPTH24_STENCIL8 },
                           { DEPTH_BIT_8,   GL_DEPTH_COMPONENT },
                           { DEPTH_BIT_16,  GL_DEPTH_COMPONENT16 },
                           { DEPTH_BIT_24,  GL_DEPTH_COMPONENT24 },
                           { DEPTH_BIT_32,  GL_DEPTH_COMPONENT32 },
                           { -1, -1 },
                         };
   GL_Format stencil[] = {
                           { STENCIL_NONE, 0 },
                           { STENCIL_BIT_1, GL_STENCIL_INDEX1 },
                           { STENCIL_BIT_4, GL_STENCIL_INDEX4 },
                           { STENCIL_BIT_8, GL_STENCIL_INDEX8 },
                           { -1, -1 },
                         };
#endif

   int msaa_samples[4] = {0, -1, -1, -1};  // { NO_MSAA, LOW, MED, HIGH }

   EVGL_Surface_Format *fmt = NULL;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("EVGL Engine not initialized!");
        return 0;
     }

   // Check Surface Cap for MSAA
   if (evgl_engine->caps.msaa_supported)
     {
        if ((evgl_engine->caps.msaa_samples[2] != evgl_engine->caps.msaa_samples[1]) &&
            (evgl_engine->caps.msaa_samples[2] != evgl_engine->caps.msaa_samples[0]))
             msaa_samples[3] = evgl_engine->caps.msaa_samples[2];    // HIGH
        if ((evgl_engine->caps.msaa_samples[1] != evgl_engine->caps.msaa_samples[0]))
             msaa_samples[2] = evgl_engine->caps.msaa_samples[1];    // MED
        if (evgl_engine->caps.msaa_samples[0])
             msaa_samples[1] = evgl_engine->caps.msaa_samples[0];    // LOW
     }


   // MSAA
   for ( m = 0; m < 4; m++)
     {
        if (msaa_samples[m] < 0) continue;

        // Color Formats
        i = 0;
        while ( color[i].bit >= 0 )
          {
             j = 0;
             // Depth Formats
             while ( depth[j].bit >= 0 )
               {
                  k = 0;
                  // Stencil Formats
                  while ( stencil[k].bit >= 0)
                    {
                       fmt = &evgl_engine->caps.fbo_fmts[num_fmts];
                       if (_surface_cap_test(fmt, &color[i], &depth[j], &stencil[k], msaa_samples[m]))
                          num_fmts++;
                       k++;
                    }
                  j++;
               }
             i++;
          }
     }

   return num_fmts;
}

static int
_surface_cap_load(Eet_File *ef)
{
   int res = 0, i = 0, length = 0;
   char tag[80];
   char *data = NULL;

   data = eet_read(ef, "num_fbo_fmts", &length);
   if ((!data) || (length <= 0)) goto finish;
   if (data[length - 1] != 0) goto finish;
   evgl_engine->caps.num_fbo_fmts = atoi(data);
   free(data);
   data = NULL;

   // !!!FIXME
   // Should use eet functionality instead of just reading using sscanfs...
   for (i = 0; i < evgl_engine->caps.num_fbo_fmts; ++i)
     {
        EVGL_Surface_Format *fmt = &evgl_engine->caps.fbo_fmts[i];

        snprintf(tag, sizeof(tag), "fbo_%d", i);
        data = eet_read(ef, tag, &length);
        if ((!data) || (length <= 0)) goto finish;
        if (data[length - 1] != 0) goto finish;
        sscanf(data, "%d%d%d%d%d%d%d%d%d%d",
               &(fmt->index),
               (int*)(&(fmt->color_bit)), &(fmt->color_ifmt), &(fmt->color_fmt),
               (int*)(&(fmt->depth_bit)), &(fmt->depth_fmt),
               (int*)(&(fmt->stencil_bit)), &(fmt->stencil_fmt),
               &(fmt->depth_stencil_fmt),
               &(fmt->samples));
        free(data);
        data = NULL;
     }
   res = 1;

finish:
   if (data) free(data);
   return res;
}

static int
_surface_cap_save(Eet_File *ef)
{
   int i = 0;
   char tag[80], data[80];

   snprintf(data, sizeof(data), "%d", evgl_engine->caps.num_fbo_fmts);
   if (eet_write(ef, "num_fbo_fmts", data, strlen(data) + 1, 1) < 0)
      return 0;

   // !!!FIXME
   // Should use eet functionality instead of just writing out using snprintfs...
   for (i = 0; i < evgl_engine->caps.num_fbo_fmts; ++i)
     {
        EVGL_Surface_Format *fmt = &evgl_engine->caps.fbo_fmts[i];

        snprintf(tag, sizeof(tag), "fbo_%d", i);
        snprintf(data, sizeof(data), "%d %d %d %d %d %d %d %d %d %d",
                 fmt->index,
                 fmt->color_bit, fmt->color_ifmt, fmt->color_fmt,
                 fmt->depth_bit, fmt->depth_fmt,
                 fmt->stencil_bit, fmt->stencil_fmt,
                 fmt->depth_stencil_fmt,
                 fmt->samples);
        if (eet_write(ef, tag, data, strlen(data) + 1, 1) < 0) return 0;
     }

   return 1;
}

static int
_surface_cap_cache_load()
{
   /* check eet */
   Eet_File *et = NULL;
   char cap_dir_path[PATH_MAX];
   char cap_file_path[PATH_MAX];

   if (!evas_gl_common_file_cache_dir_check(cap_dir_path, sizeof(cap_dir_path)))
      return 0;

   if (!evas_gl_common_file_cache_file_check(cap_dir_path, "surface_cap",
                                             cap_file_path, sizeof(cap_dir_path)))
      return 0;

   /* use eet */
   if (!eet_init()) return 0;
   et = eet_open(cap_file_path, EET_FILE_MODE_READ);
   if (!et) goto error;

   if (!_surface_cap_load(et))
      goto error;

   if (et) eet_close(et);
   eet_shutdown();
   return 1;

error:
   if (et) eet_close(et);
   eet_shutdown();
   return 0;
}

static int
_surface_cap_cache_save()
{
   /* check eet */
   Eet_File *et = NULL; //check eet file
   int tmpfd = -1;
   char cap_dir_path[PATH_MAX];
   char cap_file_path[PATH_MAX];
   char tmp_file_name[PATH_MAX + PATH_MAX + 128];
   Eina_Tmpstr *tmp_file_path = NULL;

   /* use eet */
   if (!eet_init()) return 0;

   if (!evas_gl_common_file_cache_dir_check(cap_dir_path, sizeof(cap_dir_path)))
     {
        if (!evas_gl_common_file_cache_mkpath(cap_dir_path))
          return 0; /* we can't make directory */
     }

   evas_gl_common_file_cache_file_check(cap_dir_path, "surface_cap", cap_file_path,
                                        sizeof(cap_dir_path));

   /* use mkstemp for writing */
   snprintf(tmp_file_name, sizeof(tmp_file_name), "%s.XXXXXX.cache", cap_file_path);
   tmpfd = eina_file_mkstemp(tmp_file_name, &tmp_file_path);
   if (tmpfd < 0) goto error;

   et = eet_open(tmp_file_path, EET_FILE_MODE_WRITE);
   if (!et) goto error;

   if (!_surface_cap_save(et)) goto error;

   if (eet_close(et) != EET_ERROR_NONE) goto destroyed;
   if (rename(tmp_file_path, cap_file_path) < 0) goto destroyed;
   eina_tmpstr_del(tmp_file_path);
   close(tmpfd);
   eet_shutdown();

   return 1;

destroyed:
   et = NULL;

error:
   if (tmpfd >= 0) close(tmpfd);
   if (et) eet_close(et);
   if (evas_gl_common_file_cache_file_exists(tmp_file_path))
     unlink(tmp_file_path);
   eina_tmpstr_del(tmp_file_path);
   eet_shutdown();
   return 0;
}

static int
_surface_cap_init(void *eng_data)
{
   int gles_version;
   int ret = 0;
   int max_size = 0;
   int max_samples = 0;

   // Do internal make current
   if (!_internal_resource_make_current(eng_data, NULL, NULL))
     {
        ERR("Error doing an internal resource make current");
        return ret;
     }

   // Query the max width and height of the surface
   glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &max_size);

   evgl_engine->caps.max_w = max_size;
   evgl_engine->caps.max_h = max_size;
   DBG("Max Surface Width: %d   Height: %d", evgl_engine->caps.max_w, evgl_engine->caps.max_h);

   gles_version = evas_gl_common_version_check(NULL);

   // Check for MSAA support
   if (gles_version == 3)
     {
        glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
        INF("MSAA support for GLES 3 is not implemented yet!");
        max_samples = 0;
     }
#ifdef GL_GLES
   else if (EXTENSION_SUPPORT(IMG_multisampled_render_to_texture))
     {
        glGetIntegerv(GL_MAX_SAMPLES_IMG, &max_samples);
     }
   else if (EXTENSION_SUPPORT(EXT_multisampled_render_to_texture))
     {
        glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples);
     }
   else
     {
        const char *exts = (const char *) glGetString(GL_EXTENSIONS);

        if (exts && strstr(exts, "EXT_multisampled_render_to_texture"))
          glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples);
        else if (exts && strstr(exts, "IMG_multisampled_render_to_texture"))
          glGetIntegerv(GL_MAX_SAMPLES_IMG, &max_samples);
     }
#endif

   if (max_samples >= 2)
     {
        evgl_engine->caps.msaa_samples[0] = 2;
        evgl_engine->caps.msaa_samples[1] = (max_samples >> 1) < 2 ? 2 : (max_samples >> 1);
        evgl_engine->caps.msaa_samples[2] = max_samples;
        evgl_engine->caps.msaa_supported  = 1;
     }

   // Load Surface Cap
   if (!_surface_cap_cache_load())
     {
        // Check Surface Cap
        evgl_engine->caps.num_fbo_fmts = _surface_cap_check();
        _surface_cap_cache_save();
        DBG("Ran Evas GL Surface Cap and Cached the existing values.");
     }
   else
     {
        DBG("Loaded cached Evas GL Surface Cap values.");
     }

   if (evgl_engine->caps.num_fbo_fmts)
     {
        _surface_cap_print(0);
        DBG("Number of supported surface formats: %d", evgl_engine->caps.num_fbo_fmts);
        ret = 1;
     }
   else
     {
        ERR("There are no available surface formats. Error!");
     }

   // Destroy internal resources
   _evgl_tls_resource_destroy(eng_data);

   return ret;
}

static int
_context_ext_check(EVGL_Context *ctx)
{
   if (!ctx)
      return 0;

   if (ctx->extension_checked)
      return 1;

#ifdef GL_GLES
   int fbo_supported = 0;
   int egl_image_supported = 0;
   int texture_image_supported = 0;

   switch (ctx->version)
     {
      case EVAS_GL_GLES_1_X:
         if (EXTENSION_SUPPORT_GLES1(framebuffer_object))
           fbo_supported = 1;
         break;
      case EVAS_GL_GLES_2_X:
      case EVAS_GL_GLES_3_X:
      default:
         fbo_supported = 1;
     }

   if (EXTENSION_SUPPORT_EGL(EGL_KHR_image_base))
     egl_image_supported = 1;
   if (EXTENSION_SUPPORT_EGL(EGL_KHR_gl_texture_2D_image))
     texture_image_supported = 1;

   if (egl_image_supported)
     {
        if (fbo_supported && texture_image_supported)
          ctx->fbo_image_supported = 1;
        else
          ctx->pixmap_image_supported = 1;
     }
#endif

   if (ctx->version == EVAS_GL_GLES_3_X)
     {
        /* HACK, as of 2017/03/08:
         * Some NVIDIA drivers pretend to support GLES 3.1 with EGL but in
         * fact none of the new functions are available, neither through
         * dlsym() nor eglGetProcAddress(). GLX/OpenGL should work though.
         * This is a fixup for glGetString(GL_VERSION).
         */
        if (!gles3_funcs->glVertexBindingDivisor)
          ctx->version_minor = 0;
        else
          ctx->version_minor = 1;
     }

   ctx->extension_checked = 1;

   return 1;
}


static const char *
_glenum_string_get(GLenum e)
{
   switch (e)
     {
      case 0:
         return "0";
      case GL_RGB:
         return "GL_RGB";
      case GL_RGBA:
         return "GL_RGBA";

#ifdef GL_GLES
         // Depth
      case GL_DEPTH_COMPONENT:
         return "GL_DEPTH_COMPONENT";
      case GL_DEPTH_COMPONENT16:
         return "GL_DEPTH_COMPONENT16";
      case GL_DEPTH_COMPONENT24_OES:
         return "GL_DEPTH_COMPONENT24_OES";
      case GL_DEPTH_COMPONENT32_OES:
       return "GL_DEPTH_COMPONENT32_OES";

         // Stencil
      case GL_STENCIL_INDEX1_OES:
         return "GL_STENCIL_INDEX1_OES";
      case GL_STENCIL_INDEX4_OES:
         return "GL_STENCIL_INDEX4_OES";
      case GL_STENCIL_INDEX8:
         return "GL_STENCIL_INDEX8";

         // Depth_Stencil
      case GL_DEPTH_STENCIL_OES:
         return "GL_DEPTH_STENCIL_OES";
      case GL_DEPTH24_STENCIL8_OES:
         return "GL_DEPTH24_STENCIL8_OES";
#else
         // Depth
      case GL_DEPTH_COMPONENT:
         return "GL_DEPTH_COMPONENT";
      case GL_DEPTH_COMPONENT16:
         return "GL_DEPTH_COMPONENT16";
      case GL_DEPTH_COMPONENT24:
         return "GL_DEPTH_COMPONENT24";
      case GL_DEPTH_COMPONENT32:
         return "GL_DEPTH_COMPONENT32";

         // Stencil
      case GL_STENCIL_INDEX:
         return "GL_STENCIL_INDEX";
      case GL_STENCIL_INDEX1:
         return "GL_STENCIL_INDEX1";
      case GL_STENCIL_INDEX4:
         return "GL_STENCIL_INDEX4";
      case GL_STENCIL_INDEX8:
         return "GL_STENCIL_INDEX8";

         // Depth_Stencil
      case GL_DEPTH_STENCIL:
        return "GL_DEPTH_STENCIL";
      case GL_DEPTH24_STENCIL8:
         return "GL_DEPTH24_STENCIL8";
#endif
      default:
         return "ERR";
     }
}

static void
_surface_cap_print(int error)
{
   int i = 0;
#define PRINT_LOG(...) \
   if (error) \
      ERR(__VA_ARGS__); \
   else \
      DBG(__VA_ARGS__);

   PRINT_LOG("----------------------------------------------------------------------------------------------------------------");
   PRINT_LOG("                 Evas GL Supported Surface Format                                                               ");
   PRINT_LOG("----------------------------------------------------------------------------------------------------------------");
   PRINT_LOG(" Max Surface Width: %d Height: %d", evgl_engine->caps.max_w, evgl_engine->caps.max_h);
   PRINT_LOG(" Multisample Support: %d", evgl_engine->caps.msaa_supported);
   //if (evgl_engine->caps.msaa_supported)
     {
        PRINT_LOG("             Low  Samples: %d", evgl_engine->caps.msaa_samples[0]);
        PRINT_LOG("             Med  Samples: %d", evgl_engine->caps.msaa_samples[1]);
        PRINT_LOG("             High Samples: %d", evgl_engine->caps.msaa_samples[2]);
     }
   PRINT_LOG("[Index] [Color Format]  [------Depth Bits------]      [----Stencil Bits---]     [---Depth_Stencil---]  [Samples]");

#define PRINT_SURFACE_CAP(IDX, COLOR, DEPTH, STENCIL, DS, SAMPLE) \
     { \
        PRINT_LOG("  %3d  %10s    %25s  %25s  %25s  %5d", IDX, _glenum_string_get(COLOR), _glenum_string_get(DEPTH), _glenum_string_get(STENCIL), _glenum_string_get(DS), SAMPLE ); \
     }

   for (i = 0; i < evgl_engine->caps.num_fbo_fmts; ++i)
     {
        EVGL_Surface_Format *fmt = &evgl_engine->caps.fbo_fmts[i];
        PRINT_SURFACE_CAP(i, fmt->color_fmt, fmt->depth_fmt, fmt->stencil_fmt, fmt->depth_stencil_fmt, fmt->samples);
     }

#undef PRINT_SURFACE_CAP
#undef PRINT_LOG
}

static void
_surface_context_list_print()
{
   Eina_List *l;
   EVGL_Surface *s;
   EVGL_Context *c;
   int count = 0;

   // Only print them when the log level is 6
   if (_evas_gl_log_level < 6) return;

#define RESET "\e[m"
#define GREEN "\e[1;32m"
#define YELLOW "\e[1;33m"
#define RED "\e[1;31m"

   LKL(evgl_engine->resource_lock);

   DBG( YELLOW "-----------------------------------------------" RESET);
   DBG("Total Number of active Evas GL Surfaces: %d", eina_list_count(evgl_engine->surfaces));

   EINA_LIST_FOREACH(evgl_engine->surfaces, l, s)
     {
        DBG( YELLOW "\t-----------------------------------------------" RESET);
        DBG( RED "\t[Surface %d]" YELLOW " Ptr: %p" RED " Appx Mem: %d Byte", count++, s, (s->buffer_mem[0]+s->buffer_mem[1]+s->buffer_mem[2]+s->buffer_mem[3]));
        DBG( GREEN "\t\t Size:" RESET " (%d, %d)",  s->w, s->h);

        if (s->buffer_mem[0])
          {
             DBG( GREEN "\t\t Color Format:" RESET " %s", _glenum_string_get(s->color_fmt));
             DBG( GREEN "\t\t Color Buffer Appx. Mem Usage:" RESET " %d Byte", s->buffer_mem[0]);
          }
        if (s->buffer_mem[1])
          {
             DBG( GREEN "\t\t Depth Format:" RESET " %s", _glenum_string_get(s->depth_fmt));
             DBG( GREEN "\t\t Depth Buffer Appx. Mem Usage: " RESET "%d Byte", s->buffer_mem[1]);
          }
        if (s->buffer_mem[2])
          {
             DBG( GREEN "\t\t Stencil Format:" RESET " %s", _glenum_string_get(s->stencil_fmt));
             DBG( GREEN "\t\t Stencil Buffer Appx. Mem Usage:" RESET " %d Byte", s->buffer_mem[2]);
          }
        if (s->buffer_mem[3])
          {
             DBG( GREEN "\t\t D-Stencil Format:" RESET " %s", _glenum_string_get(s->depth_stencil_fmt));
             DBG( GREEN "\t\t D-Stencil Buffer Appx. Mem Usage:" RESET " %d Byte", s->buffer_mem[3]);
          }
        if (s->msaa_samples)
           DBG( GREEN "\t\t MSAA Samples:" RESET " %d", s->msaa_samples);
        if (s->direct_fb_opt)
           DBG( GREEN "\t\t Direct Option Enabled" RESET );
        DBG( YELLOW "\t-----------------------------------------------" RESET);
     }

   count = 0;

   DBG( YELLOW "-----------------------------------------------" RESET);
   DBG("Total Number of active Evas GL Contexts: %d", eina_list_count(evgl_engine->contexts));
   EINA_LIST_FOREACH(evgl_engine->contexts, l, c)
     {
        DBG( YELLOW "\t-----------------------------------------------" RESET);
        DBG( RED "\t[Context %d]" YELLOW " Ptr: %p", count++, c);
     }
   DBG( YELLOW "-----------------------------------------------" RESET);

   LKU(evgl_engine->resource_lock);

#undef RESET
#undef GREEN
#undef YELLOW
#undef RED
}

//--------------------------------------------------------//
// Start from here.....
//--------------------------------------------------------//
static int
_surface_buffers_fbo_set(EVGL_Surface *sfc, GLuint fbo, Evas_GL_Context_Version version)
{
   int status;

   _framebuffer_bind(fbo, version);

   // Detach any previously attached buffers
   _texture_attach_2d(0, GL_COLOR_ATTACHMENT0, 0, 0, version);
   _renderbuffer_attach(0, GL_DEPTH_ATTACHMENT, version);
   _renderbuffer_attach(0, GL_STENCIL_ATTACHMENT, version);
#ifdef GL_GLES
   _texture_attach_2d(0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT, 0, version);
#else
    _renderbuffer_attach(0, GL_DEPTH_STENCIL_ATTACHMENT, version);
#endif

   // Render Target Texture
   if (sfc->color_buf)
     _texture_attach_2d(sfc->color_buf, GL_COLOR_ATTACHMENT0, 0, sfc->msaa_samples, version);

   // Depth Stencil RenderBuffer - Attach it to FBO
   if (sfc->depth_stencil_buf)
     {
#ifdef GL_GLES
        if (sfc->depth_stencil_fmt == GL_DEPTH_STENCIL_OES)
          _texture_attach_2d(sfc->depth_stencil_buf, GL_DEPTH_ATTACHMENT,
                             GL_STENCIL_ATTACHMENT, sfc->msaa_samples, version);
        else
          {
             _renderbuffer_attach(sfc->depth_stencil_buf, GL_DEPTH_ATTACHMENT, version);
             _renderbuffer_attach(sfc->depth_stencil_buf, GL_STENCIL_ATTACHMENT, version);
          }
#else
       _renderbuffer_attach(sfc->depth_stencil_buf, GL_DEPTH_STENCIL_ATTACHMENT, version);
#endif
     }

   // Depth RenderBuffer - Attach it to FBO
   if (sfc->depth_buf)
     _renderbuffer_attach(sfc->depth_buf, GL_DEPTH_ATTACHMENT, version);

   // Stencil RenderBuffer - Attach it to FBO
   if (sfc->stencil_buf)
     _renderbuffer_attach(sfc->stencil_buf, GL_STENCIL_ATTACHMENT, version);

   // Check FBO for completeness
   status = _framebuffer_check(version);
   if (status != GL_FRAMEBUFFER_COMPLETE)
     {
        ERR("FBO not complete. Error Code: %x!", status);
        return 0;
     }

   return 1;
}

static int
_surface_buffers_create(EVGL_Surface *sfc)
{
   // Create buffers
   if (sfc->color_fmt)
     {
        _texture_create(&sfc->color_buf);
     }


   // Depth_stencil buffers or separate buffers
   if (sfc->depth_stencil_fmt)
     {
#ifdef GL_GLES
        if (sfc->depth_stencil_fmt == GL_DEPTH_STENCIL_OES)
          _texture_create(&sfc->depth_stencil_buf);
        else
#endif
          _renderbuffer_create(&sfc->depth_stencil_buf);
     }
   else
     {
        if (sfc->depth_fmt)
          {
             _renderbuffer_create(&sfc->depth_buf);
          }
        if (sfc->stencil_fmt)
          {
             _renderbuffer_create(&sfc->stencil_buf);
          }
     }

   return 1; //ret;
}


static int
_surface_buffers_allocate(void *eng_data EINA_UNUSED, EVGL_Surface *sfc, int w, int h, Evas_GL_Context_Version version EINA_UNUSED/* unused ecept gles */)
{
   // Create buffers
   if (sfc->color_fmt)
     {
        _texture_allocate_2d(sfc->color_buf, sfc->color_ifmt, sfc->color_fmt,
                             GL_UNSIGNED_BYTE, w, h);
        if (sfc->egl_image)
          {
             _egl_image_destroy(sfc->egl_image);
             sfc->egl_image = NULL;
          }
        if ((sfc->current_ctx) && (sfc->current_ctx->fbo_image_supported) && (w) && (h))
          sfc->egl_image = _egl_image_create(sfc->current_ctx, EGL_GL_TEXTURE_2D_KHR, (void *)(uintptr_t)sfc->color_buf);

        sfc->buffer_mem[0] = w * h * 4;
     }

   // Depth_stencil buffers or separate buffers
   if (sfc->depth_stencil_fmt)
     {
#ifdef GL_GLES
        if (sfc->depth_stencil_fmt == GL_DEPTH_STENCIL_OES)
          {
             if (version == EVAS_GL_GLES_3_X)
               {
                  _texture_allocate_2d(sfc->depth_stencil_buf, GL_DEPTH24_STENCIL8_OES,
                     sfc->depth_stencil_fmt, GL_UNSIGNED_INT_24_8_OES,
                     w, h);
               }
             else
               {
                  _texture_allocate_2d(sfc->depth_stencil_buf, sfc->depth_stencil_fmt,
                     sfc->depth_stencil_fmt, GL_UNSIGNED_INT_24_8_OES,
                     w, h);
               }
          }
        else
#endif
          {
             _renderbuffer_allocate(sfc->depth_stencil_buf,
                                    sfc->depth_stencil_fmt, w, h,
                                    sfc->msaa_samples);
          }
        sfc->buffer_mem[3] = w * h * 4;
     }
   else
     {
        if (sfc->depth_fmt)
          {
             _renderbuffer_allocate(sfc->depth_buf, sfc->depth_fmt, w, h,
                                    sfc->msaa_samples);
             sfc->buffer_mem[1] = w * h * 3; // Assume it's 24 bits
          }
        if (sfc->stencil_fmt)
          {
             _renderbuffer_allocate(sfc->stencil_buf, sfc->stencil_fmt, w,
                                    h, sfc->msaa_samples);
             sfc->buffer_mem[2] = w * h; // Assume it's 8 bits
          }
     }

   return 1; //ret;
}

static int
_surface_buffers_destroy(EVGL_Surface *sfc)
{
   if (sfc->egl_image)
     {
        _egl_image_destroy(sfc->egl_image);
        sfc->egl_image = NULL;
     }
   if (sfc->color_buf)
      _texture_destroy(&sfc->color_buf);
   if (sfc->depth_buf)
      _renderbuffer_destroy(&sfc->depth_buf);
   if (sfc->stencil_buf)
      _renderbuffer_destroy(&sfc->stencil_buf);
   if (sfc->depth_stencil_buf)
     {
#ifdef GL_GLES
        if (sfc->depth_stencil_fmt == GL_DEPTH_STENCIL_OES)
          _texture_destroy(&sfc->depth_stencil_buf);
        else
#endif
          _renderbuffer_destroy(&sfc->depth_stencil_buf);
     }

   return 1;
}

static int
_internal_config_set(void *eng_data, EVGL_Surface *sfc, Evas_GL_Config *cfg)
{
   int i = 0, cfg_index = -1;
   int color_bit = 0, depth_bit = 0, stencil_bit = 0, msaa_samples = 0;
   int depth_size = 0;
   int native_win_depth = 0, native_win_stencil = 0, native_win_msaa = 0;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("Invalid EVGL Engine!");
        return 0;
     }

   // Convert Config Format to bitmask friendly format
   color_bit = (1 << cfg->color_format);
   if (cfg->depth_bits)
     {
        depth_bit = (1 << (cfg->depth_bits-1));
        depth_size = 8 * cfg->depth_bits;
     }
   if (cfg->stencil_bits) stencil_bit = (1 << (cfg->stencil_bits-1));
   if (cfg->multisample_bits)
      msaa_samples = evgl_engine->caps.msaa_samples[cfg->multisample_bits-1];

try_again:
   // Run through all the available formats and choose the first match
   for (i = 0; i < evgl_engine->caps.num_fbo_fmts; ++i)
     {
        // Check if the MSAA is supported.  Fallback if not.
        if ((msaa_samples) && (evgl_engine->caps.msaa_supported))
          {
             if (msaa_samples > evgl_engine->caps.fbo_fmts[i].samples)
                  continue;
          }

        if (color_bit & evgl_engine->caps.fbo_fmts[i].color_bit)
          {
             if (depth_bit)
               {
                  if (!(depth_bit & evgl_engine->caps.fbo_fmts[i].depth_bit))
                     continue;
               }

             if (stencil_bit)
               {
                  if (!(stencil_bit & evgl_engine->caps.fbo_fmts[i].stencil_bit))
                     continue;
               }

             // Set the surface format
             sfc->color_ifmt        = evgl_engine->caps.fbo_fmts[i].color_ifmt;
             sfc->color_fmt         = evgl_engine->caps.fbo_fmts[i].color_fmt;
             sfc->depth_fmt         = evgl_engine->caps.fbo_fmts[i].depth_fmt;
             sfc->stencil_fmt       = evgl_engine->caps.fbo_fmts[i].stencil_fmt;
             sfc->depth_stencil_fmt = evgl_engine->caps.fbo_fmts[i].depth_stencil_fmt;
             sfc->msaa_samples      = evgl_engine->caps.fbo_fmts[i].samples;

             // Direct Rendering Option
             if (cfg->options_bits & EVAS_GL_OPTIONS_DIRECT)
               {
                  Eina_Bool support_win_cfg = EINA_FALSE;

                  if (evgl_engine->funcs->native_win_surface_config_get)
                    evgl_engine->funcs->native_win_surface_config_get(eng_data, &native_win_depth, &native_win_stencil, &native_win_msaa);
                  if ((native_win_depth >= depth_size)
                      && (native_win_stencil >= stencil_bit)
                      && (native_win_msaa >= msaa_samples))
                    {
                       DBG("Win cfg can support the Req Evas GL's config successfully");
                       support_win_cfg = EINA_TRUE;
                    }
                  else
                    {
                       ERR("Win config can't support Evas GL direct rendering, "
                           "win: [depth %d, stencil %d, msaa %d] "
                           "want: [depth %d, stencil %d, msaa %d]. %s",
                           native_win_depth, native_win_stencil, native_win_msaa,
                           depth_size, stencil_bit, msaa_samples,
                           sfc->direct_override ?
                              "Forcing direct rendering anyway." :
                              "Falling back to indirect rendering (FBO).");
                       support_win_cfg = EINA_FALSE;
                    }

                  if (sfc->direct_override || support_win_cfg)
                    {
                       sfc->direct_fb_opt = EINA_TRUE;

                       // Extra flags for direct rendering
                       sfc->client_side_rotation = !!(cfg->options_bits & EVAS_GL_OPTIONS_CLIENT_SIDE_ROTATION);
                       sfc->alpha = (cfg->color_format == EVAS_GL_RGBA_8888);
                    }
                  else
                    {
                       const char *s1[] = { "", ":depth8", ":depth16", ":depth24", ":depth32" };
                       const char *s2[] = { "", ":stencil1", ":stencil2", ":stencil4", ":stencil8", ":stencil16" };
                       const char *s3[] = { "", ":msaa_low", ":msaa_mid", ":msaa_high" };
                       INF("Can not enable direct rendering with depth %d, stencil %d "
                           "and MSAA %d. When using Elementary GLView, try to call "
                           "elm_config_accel_preference_set(\"opengl%s%s%s\") before "
                           "creating any window.",
                           depth_size, stencil_bit, msaa_samples,
                           s1[cfg->depth_bits], s2[cfg->stencil_bits], s3[cfg->multisample_bits]);
                    }

                  // When direct rendering is enabled, FBO configuration should match
                  // window surface configuration as FBO will be used in fallback cases.
                  // So we search again for the formats that match window surface's.
                  if (sfc->direct_fb_opt &&
                      ((native_win_depth != depth_size) ||
                       (native_win_stencil != stencil_bit) ||
                       (native_win_msaa != msaa_samples)))
                    {
                       if (native_win_depth < 8) depth_bit = 0;
                       else depth_bit = (1 << ((native_win_depth / 8) - 1));
                       depth_size = native_win_depth;
                       stencil_bit = native_win_stencil;
                       msaa_samples = native_win_msaa;
                       goto try_again;
                    }
               }

             cfg_index = i;
             break;
          }
     }

   if (cfg_index < 0)
     {
        ERR("Unable to find a matching config format (depth:%d, stencil:%d, msaa:%d)",
            depth_size, stencil_bit, msaa_samples);
        if ((stencil_bit > 8) || (depth_size > 24))
          {
             INF("Please note that Evas GL might not support 32-bit depth or "
                 "16-bit stencil buffers, so depth24, stencil8 are the maximum "
                 "recommended values.");
             if (depth_size > 24)
               {
                  depth_bit = 4; // see DEPTH_BIT_24
                  depth_size = 24;
               }
             if (stencil_bit > 8) stencil_bit = 8; // see STENCIL_BIT_8
             DBG("Fallback to depth:%d, stencil:%d", depth_size, stencil_bit);
             goto try_again;
          }
        else if (msaa_samples > 0)
          {
             msaa_samples /= 2;
             if (msaa_samples == 1) msaa_samples = 0;
             DBG("Fallback to msaa:%d", msaa_samples);
             goto try_again;
          }
        return 0;
     }
   else
     {
        DBG("-------------Evas GL Surface Config---------------");
        DBG("Selected Config Index: %d", cfg_index);
        DBG("  Color Format     : %s", _glenum_string_get(sfc->color_fmt));
        DBG("  Depth Format     : %s", _glenum_string_get(sfc->depth_fmt));
        DBG("  Stencil Format   : %s", _glenum_string_get(sfc->stencil_fmt));
        DBG("  D-Stencil Format : %s", _glenum_string_get(sfc->depth_stencil_fmt));
        DBG("  MSAA Samples     : %d", sfc->msaa_samples);
        DBG("  Direct Option    : %d%s", sfc->direct_fb_opt, sfc->direct_override ? " (override)" : "");
        DBG("  Client-side Rot. : %d", sfc->client_side_rotation);
        DBG("--------------------------------------------------");
        sfc->cfg_index = cfg_index;
        return 1;
     }
}

static int
_evgl_direct_renderable(EVGL_Resource *rsc, EVGL_Surface *sfc)
{
   if (evgl_engine->direct_force_off) return 0;
   if (rsc->id != evgl_engine->main_tid) return 0;
   if (!sfc->direct_fb_opt) return 0;
   if (!rsc->direct.enabled) return 0;

   return 1;
}

//---------------------------------------------------------------//
// Functions used by Evas GL module
//---------------------------------------------------------------//
EVGL_Resource *
_evgl_tls_resource_get(void)
{
   EVGL_Resource *rsc = NULL;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("Invalid EVGL Engine!");
        return NULL;
     }

   if (evgl_engine->resource_key)
     rsc = eina_tls_get(evgl_engine->resource_key);

   return rsc;
}

static void
_evgl_tls_resource_destroy_cb(void *data)
{
   EVGL_Resource *rsc = data;

   LKL(evgl_engine->resource_lock);
   evgl_engine->resource_list = eina_list_remove(evgl_engine->resource_list, rsc);
   LKU(evgl_engine->resource_lock);

   _internal_resources_destroy(rsc->current_eng, rsc);
}

EVGL_Resource *
_evgl_tls_resource_create(void *eng_data)
{
   EVGL_Resource *rsc;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("Invalid EVGL Engine!");
        return NULL;
     }

   // Initialize Resource TLS
   if (!evgl_engine->resource_key && eina_tls_cb_new(&evgl_engine->resource_key, _evgl_tls_resource_destroy_cb) == EINA_FALSE)
     {
        ERR("Error creating tls key");
        return NULL;
     }
   DBG("TLS KEY created: %d", evgl_engine->resource_key);

   // Create internal resources if it hasn't been created already
   if (!(rsc = _internal_resources_create(eng_data)))
     {
        ERR("Error creating internal resources.");
        return NULL;
     }

   // Set the resource in TLS
   if (eina_tls_set(evgl_engine->resource_key, (void*)rsc) == EINA_TRUE)
     {
        // Add to the resource resource list for cleanup
        LKL(evgl_engine->resource_lock);
        evgl_engine->resource_list = eina_list_prepend(evgl_engine->resource_list, rsc);
        LKU(evgl_engine->resource_lock);
        return rsc;
     }
   else
     {
        ERR("Failed setting TLS Resource");
        _internal_resources_destroy(eng_data, rsc);
        return NULL;
     }
}

void
_evgl_tls_resource_destroy(void *eng_data)
{
   Eina_List *l;
   EVGL_Resource *rsc;
   EVGL_Surface *sfc;
   EVGL_Context *ctx;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("Invalid EVGL Engine!");
        return;
     }

   EINA_LIST_FOREACH(evgl_engine->surfaces, l, sfc)
     {
        evgl_surface_destroy(eng_data, sfc);
     }
   EINA_LIST_FOREACH(evgl_engine->contexts, l, ctx)
     {
        evgl_context_destroy(eng_data, ctx);
     }

   LKL(evgl_engine->resource_lock);

   eina_list_free(evgl_engine->surfaces);
   evgl_engine->surfaces = NULL;

   eina_list_free(evgl_engine->contexts);
   evgl_engine->contexts = NULL;

   EINA_LIST_FOREACH(evgl_engine->resource_list, l, rsc)
     {
        _internal_resources_destroy(eng_data, rsc);
     }
   eina_list_free(evgl_engine->resource_list);
   evgl_engine->resource_list = NULL;

   LKU(evgl_engine->resource_lock);

   // Destroy TLS
   if (evgl_engine->resource_key)
      eina_tls_free(evgl_engine->resource_key);
   evgl_engine->resource_key = 0;
}

EAPI void * /* EVGL_Context */
evas_gl_common_current_context_get(void)
{
   EVGL_Resource *rsc;

   if (!(rsc = _evgl_tls_resource_get()))
     {
        ERR("No current context set.");
        return NULL;
     }
   else
     return rsc->current_ctx;
}

EAPI void *
evgl_current_native_context_get(EVGL_Context *ctx)
{
   EVGLNative_Context context;

   if (!ctx)
     return NULL;

   context = ctx->context;

   if ((ctx->pixmap_image_supported) && evgl_direct_rendered())
     context = ctx->indirect_context;

   return context;
}

int
_evgl_not_in_pixel_get(void)
{
   EVGL_Resource *rsc;
   EVGL_Context *ctx;

   if (!(rsc=_evgl_tls_resource_get()))
     return 1;

   if (rsc->id != evgl_engine->main_tid)
     return 0;

   ctx = rsc->current_ctx;
   if (!ctx || !ctx->current_sfc)
     return 0;

   // if indirect rendering, we don't care. eg. elm_glview's init cb
   if (!ctx->current_sfc->direct_fb_opt)
     return 0;

   return !rsc->direct.in_get_pixels;
}

int
_evgl_direct_enabled(void)
{
   EVGL_Resource *rsc;
   EVGL_Surface  *sfc;

   if (!(rsc=_evgl_tls_resource_get())) return 0;
   if (!(rsc->current_ctx)) return 0;
   if (!(sfc=rsc->current_ctx->current_sfc)) return 0;

   return _evgl_direct_renderable(rsc, sfc);
}

EAPI void
evas_gl_common_error_set(int error_enum)
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get()))
     {
        WRN("evgl: Unable to set error!");
        return;
     }

   rsc->error_state = error_enum;
}

EAPI int
evas_gl_common_error_get(void)
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get()))
     {
        WRN("evgl: Unable to get error!");
        return EVAS_GL_NOT_INITIALIZED;
     }

   return rsc->error_state;
}

EVGLNative_Context
_evgl_native_context_get(Evas_GL_Context *ctx)
{
   EVGL_Context *evglctx;

   if (!glsym_evas_gl_native_context_get)
     {
        ERR("Engine can't get a pointer to the native context");
        return NULL;
     }

   evglctx = glsym_evas_gl_native_context_get(ctx);
   if (!evglctx) return NULL;
   return evgl_current_native_context_get(evglctx);
}

void *
_evgl_engine_data_get(Evas_GL *evasgl)
{
   if (!glsym_evas_gl_engine_data_get)
     {
        ERR("Engine can't get a pointer to the native display");
        return NULL;
     }

   return glsym_evas_gl_engine_data_get(evasgl);
}

//---------------------------------------------------------------//
// Exported functions for evas_engine to use

// Initialize engine
//    - Allocate the engine struct
//    - Assign engine funcs form evas_engine
//    - Create internal resources: internal context, surface for resource creation
//    - Initialize extensions
//    - Check surface capability
//
// This code should be called during eng_setup() in evas_engine
EVGL_Engine *
evgl_engine_init(void *eng_data, const EVGL_Interface *efunc)
{
   int direct_off = 0, direct_soff = 0, debug_mode = 0;
   char *s = NULL;

   if (evgl_engine) return evgl_engine;

   // Initialize Log Domain
   if (_evas_gl_log_dom < 0)
      _evas_gl_log_dom = eina_log_domain_register("EvasGL", EVAS_DEFAULT_LOG_COLOR);
   if (_evas_gl_log_dom < 0)
     {
        EINA_LOG_ERR("Can not create a module log domain.");
        return NULL;
     }

   // Store the Log Level
   _evas_gl_log_level = eina_log_domain_level_get("EvasGL");

   // Check the validity of the efunc
   if ((!efunc) ||
       (!efunc->surface_create) ||
       (!efunc->context_create) ||
       (!efunc->make_current))
     {
        ERR("Invalid Engine Functions for Evas GL Engine.");
        return NULL;
     }

   // Allocate engine
   evgl_engine = calloc(1, sizeof(EVGL_Engine));
   if (!evgl_engine)
     {
        ERR("Error allocating EVGL Engine. GL initialization failed.");
        goto error;
     }

   LKI(evgl_engine->resource_lock);

   // Assign functions
   evgl_engine->funcs = efunc;
   evgl_engine->safe_extensions = eina_hash_string_small_new(NULL);

   // Main Thread ID
   evgl_engine->main_tid = eina_thread_self();

   // Surface Caps
   if (!_surface_cap_init(eng_data))
     {
        ERR("Error initializing surface cap");
        goto error;
     }

   // NOTE: Should we finally remove these variables?
   // Check for Direct rendering override env var.
   if ((s = getenv("EVAS_GL_DIRECT_OVERRIDE")))
     {
        WRN("EVAS_GL_DIRECT_OVERRIDE is set to '%s' for the whole application. "
            "This should never be done except for debugging purposes.", s);
        if (atoi(s) == 1)
          evgl_engine->direct_override = 1;
     }

   // Check if Direct Rendering Memory Optimzation flag is on
   // Creates resources on demand when it fallsback to fbo rendering
   if ((s = getenv("EVAS_GL_DIRECT_MEM_OPT")))
     {
        WRN("EVAS_GL_DIRECT_MEM_OPT is set to '%s' for the whole application. "
            "This should never be done except for debugging purposes.", s);
        if (atoi(s) == 1)
          evgl_engine->direct_mem_opt = 1;
     }

   // Check if Direct Rendering Override Force Off flag is on
   s = getenv("EVAS_GL_DIRECT_OVERRIDE_FORCE_OFF");
   if (s) direct_off = atoi(s);
   if (direct_off == 1)
      evgl_engine->direct_force_off = 1;

   // Check if Direct Rendering Override Force Off flag is on
   s = getenv("EVAS_GL_DIRECT_SCISSOR_OFF");
   if (s) direct_soff = atoi(s);
   if (direct_soff == 1)
      evgl_engine->direct_scissor_off = 1;

   // Check if API Debug mode is on
   s = getenv("EVAS_GL_API_DEBUG");
   if (s) debug_mode = atoi(s);
   if (debug_mode == 1)
      evgl_engine->api_debug_mode = 1;

   return evgl_engine;

error:
   ERR("Failed to initialize EvasGL!");
   if (evgl_engine)
     {
        eina_hash_free(evgl_engine->safe_extensions);
        if (evgl_engine->resource_key)
          eina_tls_free(evgl_engine->resource_key);
        LKD(evgl_engine->resource_lock);
        free(evgl_engine);
     }
   evgl_engine = NULL;
   return NULL;
}

// Terminate engine and all the resources
//    - destroy all internal resources
//    - free allocated engine struct
void
evgl_engine_shutdown(void *eng_data)
{
   // Check if engine is valid
   if (!evgl_engine)
     {
        EINA_LOG_INFO("EvasGL Engine is not initialized.");
        return;
     }

   if (evgl_engine->safe_extensions)
     eina_hash_free(evgl_engine->safe_extensions);
   evgl_engine->safe_extensions = NULL;

   if (gles1_funcs) free(gles1_funcs);
   if (gles2_funcs) free(gles2_funcs);
   if (gles3_funcs) free(gles3_funcs);
   gles1_funcs = NULL;
   gles2_funcs = NULL;
   gles3_funcs = NULL;

   // Destroy internal resources
   _evgl_tls_resource_destroy(eng_data);

   LKD(evgl_engine->resource_lock);

   // Log
   eina_log_domain_unregister(_evas_gl_log_dom);
   _evas_gl_log_dom = -1;

   // Free engine
   free(evgl_engine);
   evgl_engine = NULL;
}

void *
evgl_surface_create(void *eng_data, Evas_GL_Config *cfg, int w, int h)
{
   EVGL_Surface *sfc = NULL;
   Eina_Bool dbg;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("Invalid EVGL Engine!");
        evas_gl_common_error_set(EVAS_GL_BAD_ACCESS);
        return NULL;
     }
   dbg = evgl_engine->api_debug_mode;

   if (!cfg)
     {
        ERR("Invalid Config!");
        evas_gl_common_error_set(EVAS_GL_BAD_CONFIG);
        return NULL;
     }

   // Check the size of the surface
   if ((w > evgl_engine->caps.max_w) || (h > evgl_engine->caps.max_h))
     {
        ERR("Requested surface size [%d, %d] is greater than max supported size [%d, %d]",
             w, h, evgl_engine->caps.max_w, evgl_engine->caps.max_h);
        evas_gl_common_error_set(EVAS_GL_BAD_PARAMETER);
        return NULL;
     }

   // Allocate surface structure
   sfc = calloc(1, sizeof(EVGL_Surface));
   if (!sfc)
     {
        ERR("Surface allocation failed.");
        evas_gl_common_error_set(EVAS_GL_BAD_ALLOC);
        goto error;
     }

   // Set surface info
   sfc->w = w;
   sfc->h = h;

   // Extra options for DR
   if (cfg->options_bits & EVAS_GL_OPTIONS_DIRECT_MEMORY_OPTIMIZE)
     {
        DBG("Setting DIRECT_MEMORY_OPTIMIZE bit");
        sfc->direct_mem_opt = EINA_TRUE;
     }
   else if (evgl_engine->direct_mem_opt == 1)
     sfc->direct_mem_opt = EINA_TRUE;

   if (cfg->options_bits & EVAS_GL_OPTIONS_DIRECT_OVERRIDE)
     {
        DBG("Setting DIRECT_OVERRIDE bit");
        sfc->direct_override = EINA_TRUE;
     }
   else if (evgl_engine->direct_override == 1)
     sfc->direct_override = EINA_TRUE;

   // Set the internal config value
   if (!_internal_config_set(eng_data, sfc, cfg))
     {
        ERR("Unsupported Format!");
        evas_gl_common_error_set(EVAS_GL_BAD_CONFIG);
        goto error;
     }
   sfc->cfg = cfg;

   // Keep track of all the created surfaces
   LKL(evgl_engine->resource_lock);
   evgl_engine->surfaces = eina_list_prepend(evgl_engine->surfaces, sfc);
   LKU(evgl_engine->resource_lock);

   if (dbg) DBG("Created surface sfc %p (eng %p)", sfc, eng_data);

   _surface_context_list_print();

   return sfc;

error:
   if (sfc) free(sfc);
   return NULL;
}

void *
evgl_pbuffer_surface_create(void *eng_data, Evas_GL_Config *cfg,
                            int w, int h, const int *attrib_list)
{
   EVGL_Surface *sfc = NULL;
   void *pbuffer;
   Eina_Bool dbg;

   // Check if engine is valid
   if (!evgl_engine)
     {
        ERR("Invalid EVGL Engine!");
        evas_gl_common_error_set(EVAS_GL_BAD_ACCESS);
        return NULL;
     }
   dbg = evgl_engine->api_debug_mode;

   if (!cfg)
     {
        ERR("Invalid Config!");
        evas_gl_common_error_set(EVAS_GL_BAD_CONFIG);
        return NULL;
     }

   if (!evgl_engine->funcs->pbuffer_surface_create)
     {
        ERR("Engine can not create PBuffers");
        evas_gl_common_error_set(EVAS_GL_NOT_INITIALIZED);
        return NULL;
     }

   // Allocate surface structure
   sfc = calloc(1, sizeof(EVGL_Surface));
   if (!sfc)
     {
        ERR("Surface allocation failed.");
        evas_gl_common_error_set(EVAS_GL_BAD_ALLOC);
        goto error;
     }

   sfc->w = w;
   sfc->h = h;
   sfc->pbuffer.color_fmt = cfg->color_format;
   sfc->pbuffer.is_pbuffer = EINA_TRUE;

   // If the surface is defined as RGB or RGBA, then create an FBO
   if (sfc->pbuffer.color_fmt == EVAS_GL_NO_FBO)
      sfc->buffers_skip_allocate = 1;

   if (!sfc->buffers_skip_allocate)
     {
        // Set the internal config value
        if (!_internal_config_set(eng_data, sfc, cfg))
          {
             ERR("Unsupported Format!");
             evas_gl_common_error_set(EVAS_GL_BAD_CONFIG);
             goto error;
          }
     }
   sfc->cfg = cfg;

   pbuffer = evgl_engine->funcs->pbuffer_surface_create
     (eng_data, sfc, attrib_list);

   if (!pbuffer)
     {
        ERR("Engine failed to create a PBuffer");
        goto error;
     }

   sfc->pbuffer.native_surface = pbuffer;

   // Keep track of all the created surfaces
   LKL(evgl_engine->resource_lock);
   evgl_engine->surfaces = eina_list_prepend(evgl_engine->surfaces, sfc);
   LKU(evgl_engine->resource_lock);

   if (dbg) DBG("Created PBuffer surface sfc %p:%p (eng %p)", sfc, pbuffer, eng_data);

   return sfc;

error:
   free(sfc);
   return NULL;
}

int
evgl_surface_destroy(void *eng_data, EVGL_Surface *sfc)
{
   EVGL_Resource *rsc;
   Eina_List *l;
   EVGL_Context *ctx;
   Eina_Bool dbg;

   // Check input parameter
   if ((!evgl_engine) || (!sfc))
     {
        ERR("Invalid input data.  Engine: %p  Surface:%p", evgl_engine, sfc);
        return 0;
     }

   if ((dbg = evgl_engine->api_debug_mode))
     DBG("Destroying surface sfc %p (eng %p)", sfc, eng_data);

   // Retrieve the resource object
   rsc = _evgl_tls_resource_get();
   if (rsc && rsc->current_ctx)
     {
        // Make current to current context to destroy surface buffers
        if (!_internal_resource_make_current(eng_data, sfc, rsc->current_ctx))
          {
             ERR("Error doing an internal resource make current");
             return 0;
          }

        // Destroy created buffers
        if (!_surface_buffers_destroy(sfc))
          {
             ERR("Error deleting surface resources.");
             return 0;
          }

        if (rsc->current_ctx->current_sfc == sfc)
          {
             if (evgl_engine->api_debug_mode)
               {
                  ERR("The surface is still current before it's being destroyed.");
                  ERR("Doing make_current(NULL, NULL)");
               }
             else
               {
                  WRN("The surface is still current before it's being destroyed.");
                  WRN("Doing make_current(NULL, NULL)");
               }
             evgl_make_current(eng_data, NULL, NULL);
          }
     }

   // Destroy indirect surface
   if (sfc->indirect)
     {
        int ret;
        if (dbg) DBG("sfc %p is used for indirect rendering", sfc);

        if (!evgl_engine->funcs->indirect_surface_destroy)
          {
             ERR("Error destroying indirect surface");
             return 0;
          }

        DBG("Destroying special surface used for indirect rendering");
        ret = evgl_engine->funcs->indirect_surface_destroy(eng_data, sfc);

        if (!ret)
          {
             ERR("Engine failed to destroy indirect surface.");
             return ret;
          }
     }

   // Destroy PBuffer surface
   if (sfc->pbuffer.native_surface)
     {
        int ret;
        if (dbg) DBG("Surface sfc %p is a pbuffer: %p", sfc, sfc->pbuffer.native_surface);

        if (!evgl_engine->funcs->pbuffer_surface_destroy)
          {
             ERR("Error destroying PBuffer surface");
             return 0;
          }

        DBG("Destroying PBuffer surface");
        ret = evgl_engine->funcs->pbuffer_surface_destroy(eng_data, sfc->pbuffer.native_surface);

        if (!ret)
          {
             ERR("Engine failed to destroy the PBuffer.");
             return ret;
          }
     }

   if (dbg) DBG("Calling make_current(NULL, NULL)");
   if (!evgl_engine->funcs->make_current(eng_data, NULL, NULL, 0))
     {
        ERR("Error doing make_current(NULL, NULL).");
        return 0;
     }

   // Remove it from the list
   LKL(evgl_engine->resource_lock);
   evgl_engine->surfaces = eina_list_remove(evgl_engine->surfaces, sfc);
   LKU(evgl_engine->resource_lock);

   EINA_LIST_FOREACH(evgl_engine->contexts, l, ctx)
     {
        if (ctx->current_sfc == sfc)
          ctx->current_sfc = NULL;
     }

   free(sfc);

   _surface_context_list_print();

   return 1;

}

void *
evgl_context_create(void *eng_data, EVGL_Context *share_ctx,
                    Evas_GL_Context_Version version,
                    void *(*native_context_get)(void *),
                    void *(*engine_data_get)(void *))
{
   EVGL_Context *ctx   = NULL;

   // A little bit ugly. But it works even when dlsym(DEFAULT) doesn't work.
   glsym_evas_gl_native_context_get = native_context_get;
   glsym_evas_gl_engine_data_get = engine_data_get;

   // Check the input
   if (!evgl_engine)
     {
        ERR("Invalid EVGL Engine!");
        evas_gl_common_error_set(EVAS_GL_BAD_ACCESS);
        return NULL;
     }

   if ((version < EVAS_GL_GLES_1_X) || (version > EVAS_GL_GLES_3_X))
     {
        ERR("Invalid context version number %d", version);
        evas_gl_common_error_set(EVAS_GL_BAD_PARAMETER);
        return NULL;
     }

   if (evgl_engine->api_debug_mode)
     DBG("Creating context GLESv%d (eng = %p, shctx = %p)", version, eng_data, share_ctx);

   // Allocate context object
   ctx = calloc(1, sizeof(EVGL_Context));
   if (!ctx)
     {
        ERR("Error allocating context object.");
        evas_gl_common_error_set(EVAS_GL_BAD_ALLOC);
        return NULL;
     }

   // Set default values
   ctx->version = version;
   ctx->version_minor = 0;
   ctx->scissor_coord[0] = 0;
   ctx->scissor_coord[1] = 0;
   ctx->scissor_coord[2] = evgl_engine->caps.max_w;
   ctx->scissor_coord[3] = evgl_engine->caps.max_h;
   ctx->gl_error = GL_NO_ERROR;

   // Call engine create context
   if (share_ctx)
      ctx->context = evgl_engine->funcs->context_create(eng_data, share_ctx->context, version);
   else
      ctx->context = evgl_engine->funcs->context_create(eng_data, NULL, version);

   // Call engine create context
   if (!ctx->context)
     {
        ERR("Error creating context from the Engine.");
        free(ctx);
        return NULL;
     }

   // Keep track of all the created context
   LKL(evgl_engine->resource_lock);
   evgl_engine->contexts = eina_list_prepend(evgl_engine->contexts, ctx);
   LKU(evgl_engine->resource_lock);

   if (evgl_engine->api_debug_mode)
     DBG("Created ctx %p", ctx);

   return ctx;
}

int
evgl_context_destroy(void *eng_data, EVGL_Context *ctx)
{
   EVGL_Resource *rsc;
   Eina_Bool dbg;

   // Check the input
   if ((!evgl_engine) || (!ctx))
     {
        ERR("Invalid input data.  Engine: %p  Context:%p", evgl_engine, ctx);
        return 0;
     }

   // Retrieve the resource object
   if (!(rsc = _evgl_tls_resource_get()))
     {
        ERR("Error retrieving resource from TLS");
        return 0;
     }

   if ((dbg = evgl_engine->api_debug_mode))
     DBG("Destroying context (eng = %p, ctx = %p)", eng_data, ctx);

   if ((rsc->current_ctx) && (rsc->current_ctx == ctx))
     {
        if (evgl_engine->api_debug_mode)
          {
             ERR("The context is still current before it's being destroyed. "
                 "Calling make_current(NULL, NULL)");
          }
        else
          {
             WRN("The context is still current before it's being destroyed. "
                 "Calling make_current(NULL, NULL)");
          }
        evgl_make_current(eng_data, NULL, NULL);
     }

   if (ctx->current_sfc && (ctx->current_sfc->current_ctx == ctx))
     ctx->current_sfc->current_ctx = NULL;

   // Delete the FBO
   if (ctx->surface_fbo)
     {
        // Set the context current with resource context/surface
        if (!_internal_resource_make_current(eng_data, ctx->current_sfc, ctx))
          {
             ERR("Error doing an internal resource make current");
             return 0;
          }
        glDeleteFramebuffers(1, &ctx->surface_fbo);
     }

   // Retrieve the resource object
   rsc = _evgl_tls_resource_get();
   if (rsc && rsc->current_ctx == ctx)
     {
        // Unset the currrent context
        if (dbg) DBG("Calling make_current(NULL, NULL)");
        if (!evgl_engine->funcs->make_current(eng_data, NULL, NULL, 0))
          {
             ERR("Error doing make_current(NULL, NULL).");
             return 0;
          }
        rsc->current_ctx = NULL;
     }

   // Destroy indirect rendering context
   if (ctx->indirect_context &&
       !evgl_engine->funcs->context_destroy(eng_data, ctx->indirect_context))
     {
        ERR("Error destroying the indirect context.");
        return 0;
     }

   // Destroy engine context
   if (!evgl_engine->funcs->context_destroy(eng_data, ctx->context))
     {
        ERR("Error destroying the engine context.");
        return 0;
     }

   // Remove it from the list
   LKL(evgl_engine->resource_lock);
   evgl_engine->contexts = eina_list_remove(evgl_engine->contexts, ctx);
   LKU(evgl_engine->resource_lock);

   // Free context
   free(ctx);

   return 1;
}

int
evgl_make_current(void *eng_data, EVGL_Surface *sfc, EVGL_Context *ctx)
{
   Eina_Bool dbg = EINA_FALSE;
   EVGL_Resource *rsc;
   int curr_fbo = 0, curr_draw_fbo = 0, curr_read_fbo = 0;
   Eina_Bool ctx_changed = EINA_FALSE;

   // Check the input validity. If either sfc is valid but ctx is NULL, it's also error.
   // sfc can be NULL as evas gl supports surfaceless make current
   if ( (!evgl_engine) || ((sfc) && (!ctx)) )
     {
        ERR("Invalid Input: Engine: %p, Surface: %p, Context: %p", evgl_engine, sfc, ctx);
        if (!evgl_engine) evas_gl_common_error_set(EVAS_GL_NOT_INITIALIZED);
        if (!ctx) evas_gl_common_error_set(EVAS_GL_BAD_CONTEXT);
        return 0;
     }

   // Get TLS Resources
   rsc = _evgl_tls_resource_get();
   if (!rsc)
     {
        DBG("Creating new TLS for this thread: %lu", (unsigned long)eina_thread_self());
        rsc = _evgl_tls_resource_create(eng_data);
        if (!rsc) return 0;
     }

   // Abuse debug mode - extra tracing for make_current and friends
   dbg = evgl_engine->api_debug_mode;
   if (dbg) DBG("(eng = %p, sfc = %p, ctx = %p), rsc = %p", eng_data, sfc, ctx, rsc);

   // Unset
   if ((!sfc) && (!ctx))
     {
        if (rsc->current_ctx)
          {
             if (rsc->direct.partial.enabled)
               evgl_direct_partial_render_end();

             if (rsc->current_ctx->version == EVAS_GL_GLES_3_X)
               {
                  glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curr_draw_fbo);
                  if ((rsc->current_ctx->surface_fbo == (GLuint) curr_draw_fbo) ||
                      (rsc->current_ctx->current_sfc &&
                       rsc->current_ctx->current_sfc->color_buf == (GLuint) curr_draw_fbo))
                    {
                       glBindFramebuffer(GL_FRAMEBUFFER, 0);
                       rsc->current_ctx->current_draw_fbo = 0;
                       rsc->current_ctx->current_read_fbo = 0;
                    }
               }
             else
               {
                  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &curr_fbo);
                  if ((rsc->current_ctx->surface_fbo == (GLuint) curr_fbo) ||
                      (rsc->current_ctx->current_sfc &&
                       rsc->current_ctx->current_sfc->color_buf == (GLuint) curr_fbo))
                    {
                       glBindFramebuffer(GL_FRAMEBUFFER, 0);
                       rsc->current_ctx->current_fbo = 0;
                    }
               }

          }

        if (dbg) DBG("Calling make_current(NULL, NULL)");
        if (!evgl_engine->funcs->make_current(eng_data, NULL, NULL, 0))
          {
             ERR("Error calling make_current(NULL, NULL).");
             return 0;
          }

        // FIXME -- What is this "FIXME" about?
        if (rsc->current_ctx)
          {
             rsc->current_ctx->current_sfc = NULL;
             rsc->current_ctx = NULL;
             rsc->current_eng = NULL;
          }
        if (dbg) DBG("Call to make_current(NULL, NULL) was successful.");
        return 1;
     }

   // Disable partial rendering for previous context
   if ((rsc->current_ctx) && (rsc->current_ctx != ctx))
     {
        evas_gl_common_tiling_done(NULL);
        rsc->current_ctx->partial_render = 0;
     }

   // Do a make current
   if (!_internal_resource_make_current(eng_data, sfc, ctx))
     {
        ERR("Error doing a make current with internal surface. Context: %p", ctx);
        evas_gl_common_error_set(EVAS_GL_BAD_CONTEXT);
        return 0;
     }

   if (rsc->current_ctx != ctx)
     ctx_changed = EINA_TRUE;

   rsc->current_ctx = ctx;
   rsc->current_eng = eng_data;

   // Check whether extensions are supported for the current context version
   // to use fbo & egl image passing to evas
   if (!ctx->extension_checked)
     {
        if (!evgl_api_get(eng_data, ctx->version, EINA_FALSE))
          {
             ERR("Unable to get the list of GL APIs for version %d", ctx->version);
             evas_gl_common_error_set(EVAS_GL_NOT_INITIALIZED);
             return 0;
          }

        if (!_context_ext_check(ctx))
          {
             ERR("Unable to check required extension for the current context");
             evas_gl_common_error_set(EVAS_GL_NOT_INITIALIZED);
             return 0;
          }
     }

   if (sfc) sfc->current_ctx = ctx;
   else
     {
        DBG("Performing surfaceless make current");
        glViewport(0, 0, 0, 0);
        glScissor(0, 0, 0, 0);
        rsc->direct.rendered = 0;
        goto finish;
     }

   if (!sfc->buffers_skip_allocate)
     {
        if (!sfc->color_buf && !_surface_buffers_create(sfc))
          {
             ERR("Unable to create specified surfaces.");
             evas_gl_common_error_set(EVAS_GL_BAD_ALLOC);
             return 0;
          }

        // Allocate or free resources depending on what mode (direct of fbo) it's
        // running only if the env var EVAS_GL_DIRECT_MEM_OPT is set.
        if (sfc->direct_mem_opt)
          {
             if (_evgl_direct_renderable(rsc, sfc))
               {
                  if (dbg) DBG("sfc %p is direct renderable (has buffers: %d).", sfc, (int) sfc->buffers_allocated);

                  // Destroy created resources
                  if (sfc->buffers_allocated)
                    {
                       if (!_surface_buffers_allocate(eng_data, sfc, 0, 0, ctx->version))
                         {
                            ERR("Unable to destroy surface buffers!");
                            evas_gl_common_error_set(EVAS_GL_BAD_ALLOC);
                            return 0;
                         }
                       sfc->buffers_allocated = 0;
                    }
               }
             else
               {
                  if (sfc->direct_override)
                    {
                       DBG("Not creating fallback surfaces even though it should. Use at OWN discretion!");
                    }
                  else
                    {
                       // Create internal buffers if not yet created
                       if (!sfc->buffers_allocated)
                         {
                            if (dbg) DBG("Allocating buffers for sfc %p", sfc);
                            if (!_surface_buffers_allocate(eng_data, sfc, sfc->w, sfc->h, ctx->version))
                              {
                                 ERR("Unable Create Specificed Surfaces.  Unsupported format!");
                                 evas_gl_common_error_set(EVAS_GL_BAD_ALLOC);
                                 return 0;
                              }
                            sfc->buffers_allocated = 1;
                         }
                    }
               }
          }
        else
          {
             if (!sfc->buffers_allocated)
               {
                  if (!_surface_buffers_allocate(eng_data, sfc, sfc->w, sfc->h, ctx->version))
                    {
                       ERR("Unable Create Allocate Memory for Surface.");
                       evas_gl_common_error_set(EVAS_GL_BAD_ALLOC);
                       return 0;
                    }
                  sfc->buffers_allocated = 1;
               }
          }
     }

   if (ctx->pixmap_image_supported)
     {
        if (dbg) DBG("ctx %p is GLES %d", ctx, ctx->version);
        if (_evgl_direct_renderable(rsc, sfc))
          {
             // Transition from indirect rendering to direct rendering
             if (!rsc->direct.rendered)
               {
                  // Restore viewport and scissor test to direct rendering mode
                  glViewport(ctx->viewport_direct[0], ctx->viewport_direct[1], ctx->viewport_direct[2], ctx->viewport_direct[3]);
                  if ((ctx->direct_scissor) && (!ctx->scissor_enabled))
                    glEnable(GL_SCISSOR_TEST);
               }
             if (dbg) DBG("sfc %p is direct renderable.", sfc);
             rsc->direct.rendered = 1;
          }
        else
          {
             // Transition from direct rendering to indirect rendering
             if (rsc->direct.rendered)
               {
                  glViewport(ctx->viewport_coord[0], ctx->viewport_coord[1], ctx->viewport_coord[2], ctx->viewport_coord[3]);
                  if ((ctx->direct_scissor) && (!ctx->scissor_enabled))
                    glDisable(GL_SCISSOR_TEST);
             }

             if (ctx->version == EVAS_GL_GLES_3_X)
               {
                  ctx->current_draw_fbo = 0;
                  ctx->current_read_fbo = 0;
               }
             else
               {
                  ctx->current_fbo = 0;
               }

             rsc->direct.rendered = 0;
          }
     }
   else
     {
        // Direct Rendering
        if (_evgl_direct_renderable(rsc, sfc))
          {
             if (dbg) DBG("sfc %p is direct renderable.", sfc);

             // Create FBO if it hasn't been created
             if (!ctx->surface_fbo)
               _framebuffer_create(&ctx->surface_fbo, ctx->version);

             if (ctx->version == EVAS_GL_GLES_3_X)
               {
                  // This is to transition from FBO rendering to direct rendering
                  glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curr_draw_fbo);
                  if (ctx->surface_fbo == (GLuint)curr_draw_fbo)
                    {
                       _framebuffer_draw_bind(0, ctx->version);
                       ctx->current_draw_fbo = 0;
                    }
                  else if (ctx->current_sfc && (ctx->current_sfc->pbuffer.is_pbuffer))
                    {
                       // Using the same context, we were rendering on a pbuffer
                       _framebuffer_draw_bind(0, ctx->version);
                       ctx->current_draw_fbo = 0;
                    }

                  glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curr_read_fbo);
                  if (ctx->surface_fbo == (GLuint)curr_read_fbo)
                    {
                       _framebuffer_read_bind(0, ctx->version);
                       ctx->current_read_fbo = 0;
                    }
                  else if (ctx->current_sfc && (ctx->current_sfc->pbuffer.is_pbuffer))
                    {
                       _framebuffer_read_bind(0, ctx->version);
                       ctx->current_read_fbo = 0;
                    }

                  if (ctx->current_read_fbo == 0)
                    {
                       // If master clip is set and clip is greater than 0, do partial render
                       if (rsc->direct.partial.enabled)
                         {
                            if (!ctx->partial_render)
                              {
                                 evgl_direct_partial_render_start();
                                 ctx->partial_render = 1;
                              }
                         }
                    }
               }
             else
               {
                  // This is to transition from FBO rendering to direct rendering
                  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &curr_fbo);
                  if (ctx->surface_fbo == (GLuint)curr_fbo)
                    {
                       _framebuffer_bind(0, ctx->version);
                       ctx->current_fbo = 0;
                    }
                  else if (ctx->current_sfc && (ctx->current_sfc->pbuffer.is_pbuffer))
                    {
                       // Using the same context, we were rendering on a pbuffer
                       _framebuffer_bind(0, ctx->version);
                       ctx->current_fbo = 0;
                    }

                  if (ctx->current_fbo == 0)
                    {
                       // If master clip is set and clip is greater than 0, do partial render
                       if (rsc->direct.partial.enabled)
                         {
                            if (!ctx->partial_render)
                              {
                                 evgl_direct_partial_render_start();
                                 ctx->partial_render = 1;
                              }
                         }
                    }
               }

             rsc->direct.rendered = 1;
          }
        else if (sfc->pbuffer.native_surface)
          {
             if (dbg) DBG("Surface sfc %p is a pbuffer: %p", sfc, sfc->pbuffer.native_surface);

             // Call end tiling
             if (rsc->direct.partial.enabled)
               evgl_direct_partial_render_end();

             // Bind to the previously bound buffer (may be 0)
             if (ctx->version == EVAS_GL_GLES_3_X)
               {
                  if (ctx->current_draw_fbo)
                    {
                       _framebuffer_draw_bind(ctx->current_draw_fbo, ctx->version);
                    }
               }
             else
               {
                  if (ctx->current_fbo)
                    {
                       _framebuffer_bind(ctx->current_fbo, ctx->version);
                    }
               }

             rsc->direct.rendered = 0;
          }
        else
          {
             if (dbg) DBG("Surface sfc %p is a normal surface.", sfc);

             // Create FBO if it hasn't been created
             if (!ctx->surface_fbo)
               _framebuffer_create(&ctx->surface_fbo, ctx->version);

             // Attach fbo and the buffers
             if ((ctx_changed) || (ctx->current_sfc != sfc) || (rsc->direct.rendered))
               {
                  sfc->current_ctx = ctx;
                  if ((sfc->direct_mem_opt) && (sfc->direct_override))
                    {
                       DBG("Not creating fallback surfaces even though it should. Use at OWN discretion!");
                    }
                  else
                    {
                       // If it's transitioning from direct render to fbo render
                       // Call end tiling
                       if (rsc->direct.partial.enabled)
                          evgl_direct_partial_render_end();

                       if (!_surface_buffers_fbo_set(sfc, ctx->surface_fbo, ctx->version))
                         {
                            ERR("Attaching buffers to context fbo failed. Engine: %p  Surface: %p Context FBO: %u", evgl_engine, sfc, ctx->surface_fbo);
                            evas_gl_common_error_set(EVAS_GL_BAD_CONTEXT);
                            return 0;
                         }
                    }

                  // Bind to the previously bound buffer

                  if (ctx->version == EVAS_GL_GLES_3_X)
                    {
                       if (ctx->current_draw_fbo)
                         _framebuffer_draw_bind(ctx->current_draw_fbo, ctx->version);

                       if (ctx->current_read_fbo)
                         _framebuffer_read_bind(ctx->current_read_fbo, ctx->version);
                    }
                  else
                    {
                       if (ctx->current_fbo)
                         _framebuffer_bind(ctx->current_fbo, ctx->version);
                    }
               }
             rsc->direct.rendered = 0;
          }
     }

finish:
   ctx->current_sfc = sfc;

   _surface_context_list_print();

   return 1;
}

const char *
evgl_string_query(int name)
{
   EVGL_Resource *rsc;
   int ctx_version = EVAS_GL_GLES_2_X;

   switch(name)
     {
      case EVAS_GL_EXTENSIONS:
        {
           Eina_Strbuf *extstr = eina_strbuf_new();
           const char *glstr = NULL, *eglstr = NULL, *str = NULL;
           rsc = _evgl_tls_resource_get();
           if ((rsc) && (rsc->current_ctx))
             ctx_version = rsc->current_ctx->version;
           glstr = evgl_api_ext_string_get(EINA_FALSE, ctx_version);
           if (glstr)
             eina_strbuf_append(extstr, glstr);
           eglstr = evgl_api_ext_egl_string_get();
           if (eglstr)
             eina_strbuf_append(extstr, eglstr);
           str = eina_strbuf_string_steal(extstr);
           eina_strbuf_free(extstr);
           return str;
        }

      default:
         return "";
     };
}

void
evgl_safe_extension_add(const char *name, void *funcptr)
{
   if (!name) return;

   if (evgl_engine->api_debug_mode)
     DBG("Whitelisting function [%p] %s", funcptr, name);

   if (funcptr)
     eina_hash_set(evgl_engine->safe_extensions, name, funcptr);
   else
     eina_hash_set(evgl_engine->safe_extensions, name, (void*) 0x1);
}

Eina_Bool
evgl_safe_extension_get(const char *name, void **pfuncptr)
{
   static Eina_Bool _unsafe_checked = EINA_FALSE, _unsafe = EINA_FALSE;
   void *func;

   if (!name || !*name)
     return EINA_FALSE;

   // use this for debugging or if you want to break everything
   if (!_unsafe_checked)
     {
        if (getenv("EVAS_GL_UNSAFE_EXTENSIONS"))
          _unsafe = EINA_TRUE;
     }

   if (_unsafe)
     return EINA_TRUE;

   func = eina_hash_find(evgl_engine->safe_extensions, name);
   if (!func) return EINA_FALSE;

   // this is for safe extensions of which we didn't resolve the address
   if (func == (void *) 0x1)
     {
        if (pfuncptr) *pfuncptr = NULL;
        return EINA_TRUE;
     }

   if (pfuncptr) *pfuncptr = func;
   return EINA_TRUE;
}

void *
evgl_native_surface_buffer_get(EVGL_Surface *sfc, Eina_Bool *is_egl_image)
{
   void *buf = NULL;
   *is_egl_image = EINA_FALSE;

   if (!evgl_engine)
     {
        ERR("Invalid input data.  Engine: %p", evgl_engine);
        return NULL;
     }

   if (sfc->egl_image)
     {
        buf = sfc->egl_image;
        *is_egl_image = EINA_TRUE;
     }
   else
     {
        buf = (void *)(uintptr_t)sfc->color_buf;
     }

   return buf;
}

int
evgl_native_surface_yinvert_get(EVGL_Surface *sfc)
{
   int ret = 0;
   if (!evgl_engine)
     {
        ERR("Invalid input data.  Engine: %p", evgl_engine);
        return 0;
     }

   if (sfc->indirect)
      ret = sfc->yinvert;

   return ret;
}

int
evgl_native_surface_get(EVGL_Surface *sfc, Evas_Native_Surface *ns)
{
   // Check the input
   if ((!evgl_engine) || (!ns))
     {
        ERR("Invalid input data.  Engine: %p  NS:%p", evgl_engine, ns);
        return 0;
     }

   ns->type = EVAS_NATIVE_SURFACE_EVASGL;
   ns->version = EVAS_NATIVE_SURFACE_VERSION;
   ns->data.evasgl.surface = sfc;

   return 1;
}

int
evgl_direct_rendered(void)
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get())) return 0;

   return rsc->direct.rendered;
}

/*
 * This function can tell the engine whether a surface can be directly
 * rendered to the Evas, despite any window rotation. For that purpose,
 * we let the engine know the surface flags for this texture
 */
Eina_Bool
evgl_native_surface_direct_opts_get(Evas_Native_Surface *ns,
                                    Eina_Bool *direct_render,
                                    Eina_Bool *client_side_rotation,
                                    Eina_Bool *direct_override)
{
   EVGL_Surface *sfc;

   if (direct_render) *direct_render = EINA_FALSE;
   if (direct_override) *direct_override = EINA_FALSE;
   if (client_side_rotation) *client_side_rotation = EINA_FALSE;

   if (!evgl_engine) return EINA_FALSE;
   if (!ns) return EINA_FALSE;

   if (ns->type == EVAS_NATIVE_SURFACE_EVASGL &&
            ns->data.evasgl.surface)
     {
        sfc = ns->data.evasgl.surface;
     }
   else return EINA_FALSE;

   if (evgl_engine->api_debug_mode)
     {
        DBG("Found native surface:  DR:%d CSR:%d",
            (int) sfc->direct_fb_opt, (int) sfc->client_side_rotation);
     }

   if (direct_render) *direct_render = sfc->direct_fb_opt;
   if (direct_override) *direct_override |= sfc->direct_override;
   if (client_side_rotation) *client_side_rotation = sfc->client_side_rotation;
   return EINA_TRUE;
}

void
evgl_direct_info_set(int win_w, int win_h, int rot,
                     int img_x, int img_y, int img_w, int img_h,
                     int clip_x, int clip_y, int clip_w, int clip_h,
                     int render_op, void *surface)
{
   EVGL_Resource *rsc;
   EVGL_Surface *sfc = surface;

   if (!(rsc = _evgl_tls_resource_get()))
     return;

   /* Check for direct rendering
    *
    * DR is allowed iif:
    * - Rotation == 0
    * OR: - Client-Side Rotation is set on the surface
    *     - Direct Override is set
    *
    * If the surface is not found, we assume indirect rendering.
    */

   if (sfc &&
       ((rot == 0) || sfc->client_side_rotation || sfc->direct_override))
     {
        if (evgl_engine->api_debug_mode)
          DBG("Direct rendering is enabled.");

        rsc->direct.enabled = EINA_TRUE;

        rsc->direct.win_w   = win_w;
        rsc->direct.win_h   = win_h;
        rsc->direct.rot     = rot;

        rsc->direct.img.x   = img_x;
        rsc->direct.img.y   = img_y;
        rsc->direct.img.w   = img_w;
        rsc->direct.img.h   = img_h;

        rsc->direct.clip.x  = clip_x;
        rsc->direct.clip.y  = clip_y;
        rsc->direct.clip.w  = clip_w;
        rsc->direct.clip.h  = clip_h;

        rsc->direct.render_op_copy = (render_op == EVAS_RENDER_COPY);
     }
   else
     {
        if (evgl_engine->api_debug_mode)
          DBG("Direct rendering is disabled.");

        rsc->direct.enabled = EINA_FALSE;
     }
}

void
evgl_direct_info_clear(void)
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get())) return;

   rsc->direct.enabled = EINA_FALSE;
}

void
evgl_get_pixels_pre(void)
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get())) return;

   rsc->direct.in_get_pixels = EINA_TRUE;
}

void
evgl_get_pixels_post(void)
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get())) return;

   rsc->direct.in_get_pixels = EINA_FALSE;
}

Evas_GL_API *
evgl_api_get(void *eng_data, Evas_GL_Context_Version version, Eina_Bool alloc_only)
{
   Evas_GL_API *api = NULL;
   int minor_version = 0;

   if (version == EVAS_GL_GLES_2_X)
     {
        if (!gles2_funcs) gles2_funcs = calloc(1, EVAS_GL_API_STRUCT_SIZE);
        api = gles2_funcs;
     }
   else if (version == EVAS_GL_GLES_1_X)
     {
        if (!gles1_funcs) gles1_funcs = calloc(1, EVAS_GL_API_STRUCT_SIZE);
        api = gles1_funcs;
     }
   else if (version == EVAS_GL_GLES_3_X)
     {
        if (evas_gl_common_version_check(&minor_version) < 3)
          {
             ERR("OpenGL ES 3.x is not supported.");
             return NULL;
          }
        if (!gles3_funcs) gles3_funcs = calloc(1, EVAS_GL_API_STRUCT_SIZE);
        api = gles3_funcs;
     }
   if (!api) return NULL;
   if (alloc_only && (api->version == EVAS_GL_API_VERSION))
     return api;

#ifdef GL_GLES
    if (!evgl_api_egl_ext_init(evgl_engine->funcs->proc_address_get, evgl_engine->funcs->ext_string_get(eng_data)))
      {
         ERR("EGL extensions initialization failed");
      }
#endif
   if (version == EVAS_GL_GLES_2_X)
     {
        _evgl_api_gles2_get(api, evgl_engine->api_debug_mode);
        evgl_api_gles2_ext_get(api, evgl_engine->funcs->proc_address_get, evgl_engine->funcs->ext_string_get(eng_data));
     }
   else if (version == EVAS_GL_GLES_1_X)
     {
        _evgl_api_gles1_get(api, evgl_engine->api_debug_mode);
        evgl_api_gles1_ext_get(api, evgl_engine->funcs->proc_address_get, evgl_engine->funcs->ext_string_get(eng_data));
     }
   else if (version == EVAS_GL_GLES_3_X)
     {
        void *(*get_proc_address)(const char *) = NULL;
        const char *egl_exts;

        egl_exts = evgl_engine->funcs->ext_string_get(eng_data);
        if (egl_exts && strstr(egl_exts, "EGL_KHR_get_all_proc_addresses"))
          get_proc_address = evgl_engine->funcs->proc_address_get;

        _evgl_api_gles3_get(api, get_proc_address, evgl_engine->api_debug_mode, minor_version);
        evgl_api_gles3_ext_get(api, evgl_engine->funcs->proc_address_get, evgl_engine->funcs->ext_string_get(eng_data));
     }

   return api;
}


void
evgl_direct_partial_info_set(int pres)
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get())) return;

   rsc->direct.partial.enabled  = EINA_TRUE;
   rsc->direct.partial.preserve = pres;
}

void
evgl_direct_partial_info_clear()
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get())) return;

   rsc->direct.partial.enabled = EINA_FALSE;
}

void
evgl_direct_override_get(Eina_Bool *override, Eina_Bool *force_off)
{
   if (!evgl_engine) return;
   if (override)  *override  = evgl_engine->direct_override;
   if (force_off) *force_off = evgl_engine->direct_force_off;
}

void
evgl_direct_partial_render_start()
{
   EVGL_Resource *rsc;

   if (!(rsc=_evgl_tls_resource_get())) return;

   evas_gl_common_tiling_start(NULL,
                               rsc->direct.rot,
                               rsc->direct.win_w,
                               rsc->direct.win_h,
                               rsc->direct.clip.x,
                               rsc->direct.win_h - rsc->direct.clip.y - rsc->direct.clip.h,
                               rsc->direct.clip.w,
                               rsc->direct.clip.h,
                               rsc->direct.partial.preserve);

   if (!rsc->direct.partial.preserve)
      rsc->direct.partial.preserve = GL_COLOR_BUFFER_BIT0_QCOM;
}

void
evgl_direct_partial_render_end()
{
   EVGL_Context *ctx;
   ctx = evas_gl_common_current_context_get();

   if (!ctx) return;

   if (ctx->partial_render)
     {
        evas_gl_common_tiling_done(NULL);
        ctx->partial_render = 0;
     }
}

EAPI void
evas_gl_common_context_restore_set(Eina_Bool enable)
{
   _need_context_restore = enable;
}

//-----------------------------------------------------//


