text: /*
   * System Interface Library for games
   * Copyright (c) 2007-2020 Andrew Church <achurch@achurch.org>
   * Released under the GNU GPL version 3 or later; NO WARRANTY is provided.
   * See the file COPYING.txt for details.
   *
   * src/sysdep/android/graphics.c: Graphics and rendering functionality for
   * Android.
   */
  
  #define IN_SYSDEP
  
  #include "src/base.h"
  #include "src/endian.h"
  #include "src/graphics.h"
  #include "src/math.h"
  #include "src/memory.h"
  #include "src/sysdep.h"
  #include "src/sysdep/android/internal.h"
  #include "src/sysdep/opengl/opengl.h"
  #include "src/thread.h"
  
  #include <dlfcn.h>
  #include <EGL/egl.h>
  
  /*************************************************************************/
  /****************************** Local data *******************************/
  /*************************************************************************/
  
  /* Graphics capability structure returned to high-level code.  We get the
   * actual display size at init time and return that as the only supported
   * mode. */
  
  static GraphicsDisplayModeEntry display_modes[1];
  
  static SysGraphicsInfo graphics_info = {
      .has_windowed_mode = 0,
      .num_devices = 1,
      .num_modes = 1,
      .modes = display_modes,
  };
  
  /*-----------------------------------------------------------------------*/
  
  /* Cached Java method IDs. */
  static jmethodID getDisplayWidth, getDisplayHeight, getDisplayFullWidth;
  static jmethodID getDisplayFullHeight, getDisplaySizeInches;
  
  /* Have we been initialized? */
  static uint8_t initted;
  
  /* Are we currently suspended (via android_suspend_graphics())? */
  static uint8_t suspended;
  
  /* GL display objects. */
  static EGLDisplay display;
  static EGLConfig config;
  static EGLSurface surface = EGL_NO_SURFACE;
  static EGLContext context = EGL_NO_CONTEXT;
  
  /* Display size and pixel format. */
  static int display_width, display_height;
  static EGLint display_format;
  
  /* Requested depth and stencil buffer resolution. */
  static int depth_bits;
  static int stencil_bits;
  
  /* Requested sample count for multisampling (1 = multisampling disabled). */
  static int multisample_samples = 1;
  
  /* Flag: sync to the vertical retrace on each frame? */
  static uint8_t vsync;
  
  /* Frame interval for display. */
  static int frame_interval;
  
  /* Requested OpenGL version (0 if not set). */
  static int desired_opengl_major, desired_opengl_minor;
  
  /*-----------------------------------------------------------------------*/
  
  /* Local routine declarations. */
  
  /**
   * select_egl_config:  Select an appropriate EGL configuration for the
   * current display attributes.
   *
   * Note that the configuration is returned via a pointer rather than as
   * the function's return value because zero is a valid EGLConfig value.
   *
   * [Parameters]
   *     config_ret: Pointer to variable to receive the EGL configuration.
   * [Return value]
   *     True on success, false on error.
   */
  static int select_egl_config(EGLConfig *config_ret);
  
  /**
   * create_egl_context:  Create a new EGL context and make it the current
   * context.
   *
   * [Return value]
   *     Created context, or EGL_NO_CONTEXT on error.
   */
  static EGLContext create_egl_context(void);
  
  /**
   * create_gl_shader_compilation_context:  Create and make current a new
   * EGL context for the current thread which can be used to compile shaders.
   *
   * [Return value]
   *     True on success or if the current thread already has a GL context,
   *     false on error.
   */
  static int create_gl_shader_compilation_context(void);
  
  /**
   * android_eglGetProcAddress:  Wrapper for eglGetProcAddress() which
   * preferentially looks up the symbol via dlsym(), as a workaround for
   * EGL libraries which don't return pointers for core GL functions.
   *
   * [Parameters]
   *     name: Name of function to look up.
   * [Return value]
   *     Function pointer, or NULL if the function is not found.
   */
  static void *android_eglGetProcAddress(const char *name);
  
  /*************************************************************************/
  /***************** Interface: Basic graphics operations ******************/
  /*************************************************************************/
  
  const SysGraphicsInfo *sys_graphics_init(void)
  {
      PRECOND(!initted, return NULL);
  
      getDisplayWidth = get_method(0, "getDisplayWidth", "()I");
      getDisplayHeight = get_method(0, "getDisplayHeight", "()I");
      getDisplayFullWidth = get_method(0, "getDisplayFullWidth", "()I");
      getDisplayFullHeight = get_method(0, "getDisplayFullHeight", "()I");
      getDisplaySizeInches = get_method(0, "getDisplaySizeInches", "()F");
      ASSERT(getDisplayWidth != 0, return NULL);
      ASSERT(getDisplayHeight != 0, return NULL);
      ASSERT(getDisplayFullWidth != 0, return NULL);
      ASSERT(getDisplayFullHeight != 0, return NULL);
      ASSERT(getDisplaySizeInches != 0, return NULL);
  
      display_modes[0].device = 0;
      display_modes[0].device_name = NULL;
      display_modes[0].width = android_display_width();
      display_modes[0].height = android_display_height();
      display_modes[0].refresh = 0;
      graphics_info.num_modes = 1;
  
      /* Set up EGL (making sure the UI thread doesn't get in our way). */
      android_lock_ui_thread();
      {
          display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
          if (!eglInitialize(display, NULL, NULL)) {
              DLOG("eglInitialize() failed: %d", eglGetError());
              android_unlock_ui_thread();
              return NULL;
          }
      }
      android_unlock_ui_thread();
  
      android_toggle_navigation_bar(0);
  
      depth_bits = 16;
      desired_opengl_major = 0;
      desired_opengl_minor = 0;
      stencil_bits = 0;
      multisample_samples = 1;
      vsync = 1;
      frame_interval = 1;
  
      initted = 1;
      suspended = 0;
      return &graphics_info;
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_cleanup(void)
  {
      PRECOND(initted, return);
      PRECOND(!suspended, return);
  
      android_lock_ui_thread();
      {
          if (context) {
              opengl_cleanup();
              eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE,
                             EGL_NO_CONTEXT);
              eglDestroyContext(display, context);
              context = 0;
          }
          if (surface) {
              eglDestroySurface(display, surface);
              surface = 0;
          }
          eglTerminate(display);
          display = 0;
      }
      android_unlock_ui_thread();
  
      initted = 0;
  }
  
  /*-----------------------------------------------------------------------*/
  
  int sys_graphics_device_width(void)
  {
      return display_modes[0].width;
  }
  
  /*-----------------------------------------------------------------------*/
  
  int sys_graphics_device_height(void)
  {
      return display_modes[0].height;
  }
  
  /*-----------------------------------------------------------------------*/
  
  int sys_graphics_set_display_attr(const char *name, va_list args)
  {
      if (strcmp(name, "center_window") == 0) {
          return 1;  // Meaningless on Android.
      }
  
      if (strcmp(name, "depth_bits") == 0) {
          const int value = va_arg(args, int);
          if (value < 0) {
              DLOG("Invalid value for attribute %s: %d", name, value);
              return 0;
          }
          depth_bits = value;
          return 1;
      }
  
      if (strcmp(name, "device") == 0) {
          const int value = va_arg(args, int);
          return (value == 0);
      }
  
      if (stricmp(name, "frame_interval") == 0) {
          const int new_frame_interval = va_arg(args, int);
          if (new_frame_interval <= 0) {
              DLOG("Invalid frame interval: %d", new_frame_interval);
              return 0;
          }
          frame_interval = new_frame_interval;
          eglSwapInterval(display, vsync ? frame_interval : 0);
          return 1;
      }
  
      if (strcmp(name, "multisample") == 0) {
          const int samples = va_arg(args, int);
          if (samples <= 0) {
              DLOG("Invalid value for attribute %s: %d", name, samples);
              return 0;
          }
          multisample_samples = samples;
          return 1;
      }
  
      if (strcmp(name, "opengl_debug") == 0) {
          opengl_enable_debug(va_arg(args, int) != 0);
          return 1;
      }
  
      if (strcmp(name, "opengl_version") == 0) {
          desired_opengl_major = va_arg(args, int);
          desired_opengl_minor = va_arg(args, int);
          return 1;
      }
  
      if (strcmp(name, "refresh_rate") == 0) {
          const float value = (float)va_arg(args, double);
          if (!(value >= 0)) {
              DLOG("Invalid value for attribute %s: %g", name, value);
              return 0;
          }
          return 1;
      }
  
      if (strcmp(name, "stencil_bits") == 0) {
          const int value = va_arg(args, int);
          if (value < 0) {
              DLOG("Invalid value for attribute %s: %d", name, value);
              return 0;
          }
          stencil_bits = value;
          return 1;
      }
  
      if (strcmp(name, "vsync") == 0) {
          vsync = va_arg(args, int);
          eglSwapInterval(display, vsync ? frame_interval : 0);
          return 1;
      }
  
      if (strcmp(name, "window") == 0) {
          const int window = va_arg(args, int);
          return (window == 0);  // No windows on Android.
      }
  
      if (strcmp(name, "window_resizable") == 0) {
          return 1;  // No windows on Android.
      }
  
      return 0;
  }
  
  /*-----------------------------------------------------------------------*/
  
  GraphicsError sys_graphics_set_display_mode(int width, int height)
  {
      PRECOND(!suspended, return GRAPHICS_ERROR_UNKNOWN);
  
      GraphicsError error = GRAPHICS_ERROR_UNKNOWN;
  
      const int max_width = display_modes[graphics_info.num_modes-1].width;
      const int max_height = display_modes[graphics_info.num_modes-1].height;
      if (width <= 0 || width > max_width
       || height <= 0 || height > max_height) {
          DLOG("Invalid/unsupported size: %dx%d (maximum supported: %dx%d)",
               width, height, max_width, max_height);
          error = GRAPHICS_ERROR_MODE_NOT_SUPPORTED;
          goto error_return;
      }
  
      if (UNLIKELY(!select_egl_config(&config))) {
          error = GRAPHICS_ERROR_MODE_NOT_SUPPORTED;
          goto error_return;
      }
      if (UNLIKELY(!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID,
                                       &display_format))) {
          DLOG("Failed to get visual format for configuration %p: %d",
               config, eglGetError());
          goto error_return;
      }
  
      int window_w = width, window_h = height;
      if (window_w == display_modes[0].width
       && window_h == display_modes[0].height) {
          /* If running at native resolution, set the setBuffersGeometry()
           * size parameters to zero to disable scaling. */
          window_w = window_h = 0;
      }
  
      android_lock_ui_thread();
      {
          if (context != EGL_NO_CONTEXT) {
              opengl_cleanup();
              eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE,
                             EGL_NO_CONTEXT);
              eglDestroyContext(display, context);
              context = EGL_NO_CONTEXT;
          }
          if (surface != EGL_NO_SURFACE) {
              eglDestroySurface(display, surface);
              surface = EGL_NO_SURFACE;
          }
          const int android_error = ANativeWindow_setBuffersGeometry(
              android_window, window_w, window_h, display_format);
          if (UNLIKELY(android_error != 0)) {
              DLOG("ANativeWindow_setBuffersGeometry(%d,%d,%d) failed: %d",
                   window_w, window_h, display_format, error);
              android_unlock_ui_thread();
              goto error_return;
          }
  
          surface = eglCreateWindowSurface(display, config, android_window, NULL);
          if (UNLIKELY(surface == EGL_NO_SURFACE)) {
              DLOG("eglCreateWindowSurface() failed: %d", eglGetError());
              android_unlock_ui_thread();
              goto error_return;
          }
          eglQuerySurface(display, surface, EGL_WIDTH, &display_width);
          eglQuerySurface(display, surface, EGL_HEIGHT, &display_height);
          if (display_width != width || display_height != height) {
              /* Some devices (such as the 1st-generation Kindle Fire) report
               * a size of 1x1 here instead of the actual surface size, so in
               * that case, blindly assume we got what we asked for. */
              if (display_width == 1 && display_height == 1) {
                  DLOG("WARNING: Device reported a display size of 1x1 pixel!"
                       "  Assuming we got %dx%d as requested.", width, height);
                  display_width = width;
                  display_height = height;
              } else {
                  DLOG("Failed to get requested size %dx%d (got %dx%d instead)",
                       width, height, display_width, display_height);
                  error = GRAPHICS_ERROR_MODE_NOT_SUPPORTED;
                  android_unlock_ui_thread();
                  goto error_destroy_surface;
              }
          }
      }
      android_unlock_ui_thread();
  
      context = create_egl_context();  // Calls opengl_lookup_functions().
      if (context == EGL_NO_CONTEXT) {
          DLOG("Failed to create EGL context!");
          goto error_destroy_surface;
      }
  
      if (!opengl_init(display_width, display_height,
                       OPENGL_FEATURE_DELAYED_DELETE
                     | OPENGL_FEATURE_FAST_STATIC_VBO
                     | OPENGL_FEATURE_FRAMEBUFFERS
                     | OPENGL_FEATURE_GENERATEMIPMAP)) {
          DLOG("Failed to set up OpenGL");
          error = GRAPHICS_ERROR_BACKEND_TOO_OLD;
          goto error_clear_current;
      }
  
      /* Many (all?) Android devices don't seem to support multiple EGL
       * contexts.  Try creating a second context now, and if it fails,
       * don't enable background shader compilation. */
      static uint8_t checked_second_context = 0;
      static uint8_t second_context_ok = 0;
      if (!checked_second_context) {
          checked_second_context = 1;
          DLOG("Checking background shader compilation...");
          const int thread = thread_create(
              (ThreadFunction *)create_gl_shader_compilation_context, NULL);
          if (thread) {
              second_context_ok = thread_wait(thread);
          }
          DLOG("...background shader compilation %savailable",
               second_context_ok ? "" : "NOT ");
      }
      if (second_context_ok) {
          opengl_set_compile_context_callback(
              create_gl_shader_compilation_context);
      }
  
      eglSwapInterval(display, vsync ? frame_interval : 0);
  
      return GRAPHICS_ERROR_SUCCESS;
  
    error_clear_current:
      eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    error_destroy_surface:
      eglDestroySurface(display, surface);
      surface = 0;
    error_return:
      return error;
  }
  
  /*-----------------------------------------------------------------------*/
  
  int sys_graphics_display_is_window(void)
  {
      return 0;  // No windows on Android.
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_set_window_title(UNUSED const char *title)
  {
      /* Nothing to do. */
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_set_window_icon(UNUSED SysTexture *texture)
  {
      /* Nothing to do. */
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_set_window_resize_limits(
      UNUSED int min_width, UNUSED int min_height,
      UNUSED int max_width, UNUSED int max_height,
      UNUSED int min_aspect_x, UNUSED int min_aspect_y,
      UNUSED int max_aspect_x, UNUSED int max_aspect_y)
  {
      /* Nothing to do. */
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_show_mouse_pointer(UNUSED int on)
  {
      /* Nothing to do -- Android has a mouse pointer but doesn't allow it
       * to be disabled programmatically. */
  }
  
  /*-----------------------------------------------------------------------*/
  
  int sys_graphics_get_mouse_pointer_state(void)
  {
      return 1;  // Always displayed when a mouse is in use.
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_get_frame_period(int *numerator_ret, int *denominator_ret)
  {
      *numerator_ret = 1001 * (vsync ? frame_interval : 0);
      *denominator_ret = 60000;
  }
  
  /*-----------------------------------------------------------------------*/
  
  int sys_graphics_has_focus(void)
  {
      /* There's no concept of "focus" on Android, so always return true. */
      return 1;
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_start_frame(int *width_ret, int *height_ret)
  {
      PRECOND(!suspended, return);
  
      *width_ret = display_width;
      *height_ret = display_height;
  
      if (context) {
          opengl_start_frame();
          opengl_free_dead_resources(0);
      }
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_finish_frame(void)
  {
      PRECOND(!suspended, return);
  
      if (context) {
          eglSwapBuffers(display, surface);
      }
  }
  
  /*-----------------------------------------------------------------------*/
  
  void sys_graphics_sync(int flush)
  {
      if (context) {
          opengl_sync();
          if (flush) {
              opengl_free_dead_resources(1);
          }
      }
  }
  
  /*************************************************************************/
  /*********************** Exported utility routines ***********************/
  /*************************************************************************/
  
  int android_display_width(void)
  {
      const int has_immersive = (android_api_level >= 19);
  
      /* Note that we have to do this through Java because ANativeWindow
       * returns 1x1 for some devices and/or Android versions. */
      JNIEnv *env = get_jni_env();
      jobject activity_obj = android_activity->clazz;
      jmethodID getWidth;
      if (has_immersive) {
          if (UNLIKELY(!getDisplayFullWidth)) {  // Might be called before init.
              getDisplayFullWidth = get_method(0, "getDisplayFullWidth", "()I");
              ASSERT(getDisplayFullWidth != 0, goto error);
          }
          getWidth = getDisplayFullWidth;
      } else {
          if (UNLIKELY(!getDisplayWidth)) {
              getDisplayWidth = get_method(0, "getDisplayWidth", "()I");
              ASSERT(getDisplayWidth != 0, goto error);
          }
          getWidth = getDisplayWidth;
      }
      int width = (*env)->CallIntMethod(env, activity_obj, getWidth);
      ASSERT(!clear_exceptions(env), goto error);
      ASSERT(width > 0, goto error);
      return width;
  
    error:
      return 640;
  }
  
  /*-----------------------------------------------------------------------*/
  
  int android_display_height(void)
  {
      const int has_immersive = (android_api_level >= 19);
  
      JNIEnv *env = get_jni_env();
      jobject activity_obj = android_activity->clazz;
      jmethodID getHeight;
      if (has_immersive) {
          if (UNLIKELY(!getDisplayFullHeight)) {
              getDisplayFullHeight = get_method(0, "getDisplayFullHeight", "()I");
              ASSERT(getDisplayFullHeight != 0, goto error);
          }
          getHeight = getDisplayFullHeight;
      } else {
          if (UNLIKELY(!getDisplayHeight)) {
              getDisplayHeight = get_method(0, "getDisplayHeight", "()I");
              ASSERT(getDisplayHeight != 0, goto error);
          }
          getHeight = getDisplayHeight;
      }
      int height = (*env)->CallIntMethod(env, activity_obj, getHeight);
      ASSERT(!clear_exceptions(env), goto error);
      ASSERT(height > 0, goto error);
      return height;
  
    error:
      return android_display_width()*9/16;
  }
  
  /*-----------------------------------------------------------------------*/
  
  int android_using_immersive_mode(void)
  {
      return android_api_level >= 19;
  }
  
  /*-----------------------------------------------------------------------*/
  
  float android_display_size_inches(void)
  {
      JNIEnv *env = get_jni_env();
      jobject activity_obj = android_activity->clazz;
      if (UNLIKELY(!getDisplaySizeInches)) {  // Might be called before init.
          getDisplaySizeInches = get_method(0, "getDisplaySizeInches", "()F");
          ASSERT(getDisplaySizeInches != 0, goto error);
      }
      float size = (*env)->CallFloatMethod(
          env, activity_obj, getDisplaySizeInches);
      ASSERT(!clear_exceptions(env), goto error);
      ASSERT(size > 0, goto error);
      return size;
  
    error:
      return 10.0;
  }
  
  /*-----------------------------------------------------------------------*/
  
  void android_suspend_graphics(void)
  {
      if (!initted || !context) {
          return;
      }
  
      ASSERT(!suspended, return);
  
      eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
      eglDestroySurface(display, surface);
      surface = 0;
  
      suspended = 1;
  }
  
  /*-----------------------------------------------------------------------*/
  
  void android_resume_graphics(void)
  {
      if (!initted || !suspended) {
          return;
      }
  
      ASSERT(android_window != NULL, return);
  
      ASSERT(ANativeWindow_setBuffersGeometry(
                 android_window, display_width, display_height, display_format)
             == 0);
      surface = eglCreateWindowSurface(display, config, android_window, NULL);
      ASSERT(surface != EGL_NO_SURFACE);
      ASSERT(eglMakeCurrent(display, surface, surface, context));
  
      suspended = 0;
  }
  
  /*************************************************************************/
  /**************************** Local routines *****************************/
  /*************************************************************************/
  
  static int select_egl_config(EGLConfig *config_ret)
  {
      PRECOND(config_ret != NULL, return 0);
  
      static EGLint attributes[] = {
          EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
          EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
          EGL_RED_SIZE, 8,
          EGL_GREEN_SIZE, 8,
          EGL_BLUE_SIZE, 8,
          EGL_DEPTH_SIZE, 16,
          EGL_STENCIL_SIZE, 0,
          EGL_SAMPLE_BUFFERS, 0,
          EGL_SAMPLES, 0,
          EGL_NONE
      };
  
      for (int i = 0; attributes[i] != EGL_NONE; i += 2) {
          ASSERT(i+1 < lenof(attributes), break);
          if (attributes[i] == EGL_DEPTH_SIZE) {
              attributes[i+1] = depth_bits;
          } else if (attributes[i] == EGL_STENCIL_SIZE) {
              attributes[i+1] = stencil_bits;
          } else if (attributes[i] == EGL_SAMPLE_BUFFERS) {
              attributes[i+1] = (multisample_samples > 1);
          } else if (attributes[i] == EGL_SAMPLES) {
              attributes[i+1] =
                  (multisample_samples > 1 ? multisample_samples : 0);
          }
      }
  
      EGLConfig configs[100];
      EGLint config_count = 0;
      eglChooseConfig(display, attributes, configs, lenof(configs),
                      &config_count);
      if (UNLIKELY(config_count < 1)) {
          DLOG("No valid EGL configurations found!");
          return 0;
      }
  
  #ifdef DEBUG
      for (int i = 0; i < config_count; i++) {
          EGLint config_id = 0, surface_type = -1, renderable_type = -1;
          EGLint r_size = -1, g_size = -1, b_size = -1, depth_size = -1;
          EGLint stencil_size = -1, sample_buffers = -1, samples = -1;
          EGLint caveat = -1;
          eglGetConfigAttrib(display, configs[i], EGL_CONFIG_ID,
                             &config_id);
          eglGetConfigAttrib(display, configs[i], EGL_SURFACE_TYPE,
                             &surface_type);
          eglGetConfigAttrib(display, configs[i], EGL_RENDERABLE_TYPE,
                             &renderable_type);
          eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &r_size);
          eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &g_size);
          eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &b_size);
          eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &depth_size);
          eglGetConfigAttrib(display, configs[i], EGL_STENCIL_SIZE,
                             &stencil_size);
          eglGetConfigAttrib(display, configs[i], EGL_SAMPLE_BUFFERS,
                             &sample_buffers);
          eglGetConfigAttrib(display, configs[i], EGL_SAMPLES, &samples);
          eglGetConfigAttrib(display, configs[i], EGL_CONFIG_CAVEAT, &caveat);
          DLOG("Configuration %d (%p):\n"
               "                 ID: %d\n"
               "       Surface type: 0x%X\n"
               "    Renderable type: 0x%X -%s%s%s%s%s\n"
               "     Red/green/blue: %d/%d/%d bits\n"
               "       Depth buffer: %d bits\n"
               "     Stencil buffer: %d bits\n"
               "     Sample buffers: %d (%d samples)\n"
               "             Caveat: 0x%X (%s)",
               i, configs[i], config_id, surface_type, renderable_type,
               renderable_type==0 ? "(None)" : "",
               renderable_type & EGL_OPENGL_ES_BIT ? " GLESv1" : "",
               renderable_type & EGL_OPENVG_BIT ? " VG" : "",
               renderable_type & EGL_OPENGL_ES2_BIT ? " GLESv2" : "",
               renderable_type & EGL_OPENGL_BIT ? " GL" : "",
               r_size, g_size, b_size, depth_size, stencil_size,
               sample_buffers, samples, caveat,
               (caveat==EGL_SLOW_CONFIG ? "Slow" :
                caveat==EGL_NON_CONFORMANT_CONFIG ? "Non-conformant" :
                caveat==EGL_NONE ? "No caveat" : "???"));
      }
  #endif
  
      *config_ret = configs[0];
      return 1;
  }
  
  /*-----------------------------------------------------------------------*/
  
  static EGLContext create_egl_context(void)
  {
      EGLint attribs[] = {
          EGL_CONTEXT_CLIENT_VERSION, 3,
          EGL_NONE
      };
      EGLContext this_context = eglCreateContext(display, config, NULL, attribs);
      if (this_context == EGL_NO_CONTEXT) {
          attribs[1] = 2;
          this_context = eglCreateContext(display, config, NULL, attribs);
      }
      if (UNLIKELY(this_context == EGL_NO_CONTEXT)) {
          DLOG("eglCreateContext() failed: %d", eglGetError());
          return EGL_NO_CONTEXT;
      }
  
      if (UNLIKELY(!eglMakeCurrent(display, surface, surface, this_context))) {
          DLOG("Failed to activate EGL context!");
          eglDestroyContext(display, this_context);
          return EGL_NO_CONTEXT;
      }
      opengl_lookup_functions(android_eglGetProcAddress);
  
      opengl_get_version();
      if (!opengl_version_is_at_least(desired_opengl_major,
                                      desired_opengl_minor)) {
          DLOG("EGL context version %d.%d < requested version %d.%d",
               opengl_major_version(), opengl_minor_version(),
               desired_opengl_major, desired_opengl_minor);
          eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
          eglDestroyContext(display, this_context);
          return EGL_NO_CONTEXT;
      }
  
      return this_context;
  }
  
  /*-----------------------------------------------------------------------*/
  
  static int create_gl_shader_compilation_context(void)
  {
      if (eglGetCurrentContext() != EGL_NO_CONTEXT) {
          return 1;
      }
  
      if (context == EGL_NO_CONTEXT) {
          DLOG("No main rendering context, can't create subthread context");
          return 0;
      }
      EGLContext sub_context = create_egl_context();
      if (sub_context == EGL_NO_CONTEXT) {
          DLOG("Failed to create subthread context");
          return 0;
      }
  
      /* Destroy the context now so we don't leak it when the thread exits. */
      eglDestroyContext(display, sub_context);
  
      return 1;
  }
  
  /*-----------------------------------------------------------------------*/
  
  static void *android_eglGetProcAddress(const char *name)
  {
      void *function = dlsym(RTLD_DEFAULT, name);
      if (!function) {
          DLOG("Failed to look up %s via dlsym(): %s", name, dlerror());
          function = eglGetProcAddress(name);
      }
      return function;
  }
  
  /*************************************************************************/
  /*************************************************************************/
  