/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2013-2014 - Tobias Jakobi
 *  Copyright (C) 2013-2014 - Daniel Mehrwald
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>
#include <limare.h>
#include <GLES2/gl2.h>

#include "../general.h"
#include "gfx_common.h"
#include "fonts/fonts.h"

/* Rename to LIMA_GFX_DEBUG to enable debugging code. */
#define NO_LIMA_GFX_DEBUG 1

/* Current limare only natively supports a limited amount of formats for texture  *
 * data. We compensate for this limitation by swizzling the texture data in the   *
 * pixel shader.                                                                  */

#define LIMA_TEXEL_FORMAT_BGR_565           0x0e
#define LIMA_TEXEL_FORMAT_RGBA_5551         0x0f
#define LIMA_TEXEL_FORMAT_RGBA_4444         0x10
#define LIMA_TEXEL_FORMAT_RGBA_8888         0x16

/* Limare is currently unable to deallocate individual texture objects and *
 * only allows to destroy all objects at once.                             *
 * We only create a maximum of 12 objects, before doing a full "reset", or *
 * sooner, under the condition that limare's texture memory runs out.      */
static const unsigned num_max_textures = 12;

typedef struct limare_state limare_state_t;

typedef struct limare_texture {
  unsigned width;
  unsigned height;

  int handle;
  unsigned format;

  bool rgui;
} limare_texture_t;

typedef struct vec2f {
  float x, y;
} vec2f_t;

typedef struct vec3f {
  float x, y, z;
} vec3f_t;

/* Create three shader programs. One is for displaying only the emulator core pixel data. *
 * The other two are for displaying the RGUI, where the pixel data can be provided in     *
 * two different formats. Current RetroArch only seems to ever use a single format, but   *
 * this is not set in stone, therefore making two programs necessary.                     */

typedef struct limare_data {
  limare_state_t *state;

  int program;

  int program_rgui_rgba16;
  int program_rgui_rgba32;

  float screen_aspect;
  float frame_aspect;

  unsigned upload_format;
  unsigned upload_bpp; /* bytes per pixel */

  vec3f_t *vertices;
  vec2f_t *coords;

  /* Generic buffer to create contiguous pixel data for limare
   * or to use for font blitting. */
  void *buffer;
  unsigned buffer_size;

  limare_texture_t **textures;
  unsigned texture_slots;

  limare_texture_t *cur_texture;
  limare_texture_t *cur_texture_rgui;

  unsigned font_width;
  unsigned font_height;
  limare_texture_t *font_texture;
} limare_data_t;

/* Header for simple vertex shader. */
static const char *vshader_src =
  "attribute vec4 in_vertex;\n"
  "attribute vec2 in_coord;\n"
  "\n"
  "varying vec2 coord;\n"
  "\n"
  "void main()\n"
  "{\n"
  "    gl_Position = in_vertex;\n"
  "    coord = in_coord;\n"
  "}\n";

/* Header for simple fragment shader. */
static const char *fshader_header_src =
  "precision highp float;\n"
  "\n"
  "varying vec2 coord;\n"
  "\n"
  "uniform sampler2D in_texture;\n"
  "\n";

/* Main (template) for simple fragment shader. */
static const char *fshader_main_src =
  "void main()\n"
  "{\n"
  "    vec3 pixel = texture2D(in_texture, coord)%s;\n"
  "    gl_FragColor = vec4(pixel, 1.0);\n"
  "}\n";

/* Header for RGUI fragment shader. */
/* Use mediump, which makes uColor into a (single-precision) float[4]. */
static const char *fshader_rgui_header_src =
  "precision mediump float;\n"
  "\n"
  "varying vec2 coord;\n"
  "uniform vec4 uColor;\n"
  "\n"
  "uniform sampler2D in_texture;\n"
  "\n";

/* Main (template) for RGUI fragment shader. */
static const char *fshader_rgui_main_src =
  "void main()\n"
  "{\n"
  "    vec4 pixel = texture2D(in_texture, coord)%s;\n"
  "    gl_FragColor = pixel * uColor;\n"
  "}\n";

static inline void put_pixel_rgba4444(uint16_t *p, unsigned r, unsigned g, unsigned b, unsigned a) {
  *p = (a >> 4) | ((b >> 4) << 4) | ((g >> 4) << 8) | ((r >> 4) << 12);
}

static inline unsigned align_common(unsigned i, unsigned j) {
  return (i + j - 1) & ~(j - 1);
}

static float get_screen_aspect(limare_state_t *state) {
  unsigned w = 0, h = 0;

  limare_buffer_size(state, &w, &h);

  if (w != 0 && h != 0) {
    return (float)w / (float)h;
  }

  return 0.0f;
}

static void apply_aspect(limare_data_t *pdata, float ratio) {
  vec3f_t *vertices = pdata->vertices;
  float x, y;

  if (fabsf(pdata->screen_aspect - pdata->frame_aspect) < 0.0001f) {
    x = 1.0f;
    y = 1.0f;
  } else {
    if (pdata->screen_aspect > pdata->frame_aspect) {
      x = pdata->frame_aspect / pdata->screen_aspect;
      y = 1.0f;
    } else {
      x = 1.0f;
      y = pdata->screen_aspect / pdata->frame_aspect;
    }
  }

  /* TODO: use ratio parameter */

  vertices[0].x = vertices[2].x = -x;
  vertices[1].x = vertices[3].x =  x;

  vertices[0].y = vertices[1].y = -y;
  vertices[2].y = vertices[3].y =  y;
}

static int destroy_textures(limare_data_t *pdata) {
  unsigned i;
  int ret;

  pdata->cur_texture = NULL;
  pdata->cur_texture_rgui = NULL;

  for (i = 0; i < pdata->texture_slots; ++i) {
    free(pdata->textures[i]);
    pdata->textures[i] = NULL;
  }

  ret = limare_texture_cleanup(pdata->state);
  pdata->texture_slots = 0;

  return ret;
}

static limare_texture_t *get_texture_handle(limare_data_t *pdata,
                            unsigned width, unsigned height, unsigned format) {
  unsigned i;

  format = (format == 0) ? pdata->upload_format : format;

  for (i = 0; i < pdata->texture_slots; ++i) {
    if (pdata->textures[i]->width == width &&
        pdata->textures[i]->height == height &&
        pdata->textures[i]->format == format) return pdata->textures[i];
  }

  if (pdata->texture_slots == num_max_textures) {
    /* All texture slots are used, do a reset. */
    if (destroy_textures(pdata)) {
      RARCH_ERR("video_lima: failed to reset texture storage\n");
    }
  }

  return NULL;
}

static limare_texture_t *add_texture(limare_data_t *pdata,
                            unsigned width, unsigned height,
                            const void *pixels, unsigned format) {
  int texture = -1;
  unsigned retries = 2;
  const unsigned i = pdata->texture_slots;

  format = (format == 0) ? pdata->upload_format : format;

  /* limare_texture_upload returns -1 when the upload fails for some reason. */
  while (texture == -1 && retries > 0) {
    texture = limare_texture_upload(pdata->state, pixels, width, height, format, 0);

    if (texture != -1) break;

    destroy_textures(pdata);
    retries--;
  }

  if (texture == -1) return NULL;

  /* Set magnification to linear and minification to nearest, since we will     *
   * probably only ever scale the image to larger dimensions. Also set          *
   * wrap mode for both coords to clamp, which should eliminate some artifacts. */
  limare_texture_parameters(pdata->state, texture, GL_LINEAR, GL_NEAREST,
                            GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);

  pdata->textures[i] = calloc(1, sizeof(limare_texture_t));

  pdata->textures[i]->width = width;
  pdata->textures[i]->height = height;
  pdata->textures[i]->handle = texture;
  pdata->textures[i]->format = format;

  pdata->texture_slots++;

  return pdata->textures[i];
}

static const void *make_contiguous(limare_data_t *pdata,
                                   unsigned width, unsigned height,
                                   const void *pixels, unsigned bpp,
                                   unsigned pitch) {
  unsigned i;
  unsigned full_pitch;

  bpp = (bpp == 0) ? pdata->upload_bpp : bpp;
  full_pitch = width * bpp;

  if (full_pitch == pitch) return pixels;

  RARCH_LOG("video_lima: input buffer not contiguous\n");

  /* Enlarge our buffer, if it is currently too small. */
  if (pdata->buffer_size < full_pitch * height) {
    const unsigned aligned_size = align_common(full_pitch * height, 16);

    free(pdata->buffer);
    pdata->buffer = NULL;

    posix_memalign(&pdata->buffer, 16, aligned_size);
    if (pdata->buffer == NULL) {
      RARCH_ERR("video_lima: failed to allocate buffer to make pixel data contiguous\n");
      return NULL;
    }

    pdata->buffer_size = aligned_size;
  }

  for (i = 0; i < height; ++i) {
    memcpy(pdata->buffer + i * full_pitch, pixels + i * pitch, full_pitch);
  }

  return pdata->buffer;
}

#ifdef LIMA_GFX_DEBUG
static void print_status(limare_data_t *pdata) {
  unsigned i;

  RARCH_LOG("video_lima: upload format = 0x%x, upload bpp = %u\n", pdata->upload_format, pdata->upload_bpp);
  RARCH_LOG("video_lima: buffer at %p, buffer size = %u\n", pdata->buffer, pdata->buffer_size);
  RARCH_LOG("video_lima: used texture slots = %u (from %u)\n", pdata->texture_slots, num_max_textures);

  for (i = 0; i < pdata->texture_slots; ++i) {
    RARCH_LOG("video_lima: texture slot %u, width = %u, height = %u, handle = %u, format = 0x%x\n",
              i, pdata->textures[i]->width, pdata->textures[i]->height,
              pdata->textures[i]->handle, pdata->textures[i]->format);
  }
}
#endif

static void destroy_data(limare_data_t *pdata) {
  free(pdata->vertices);
  free(pdata->coords);
}

static int setup_data(limare_data_t *pdata) {
  static const unsigned num_verts = 4;
  static const unsigned num_coords = 4 * 4;
  unsigned i;

  static const vec3f_t vertices[4] = {
    {-1.0f, -1.0f,  0.0f},
    { 1.0f, -1.0f,  0.0f},
    {-1.0f,  1.0f,  0.0f},
    { 1.0f,  1.0f,  0.0f}
  };

  static const vec2f_t coords[16] = {
    {0.0f, 1.0f}, {1.0f, 1.0f}, /*  0 degrees */
    {0.0f, 0.0f}, {1.0f, 0.0f},
    {0.0f, 0.0f}, {0.0f, 1.0f}, /* 90 degrees */
    {1.0f, 0.0f}, {1.0f, 1.0f},
    {1.0f, 0.0f}, {0.0f, 0.0f}, /* 180 degrees */
    {1.0f, 1.0f}, {0.0f, 1.0f},
    {1.0f, 1.0f}, {1.0f, 0.0f}, /* 270 degrees */
    {0.0f, 1.0f}, {0.0f, 0.0f}
  };

  pdata->vertices = calloc(num_verts, sizeof(vec3f_t));
  if (pdata->vertices == NULL) goto fail;

  pdata->coords = calloc(num_coords, sizeof(vec2f_t));
  if (pdata->coords == NULL) goto fail;

  for (i = 0; i < num_verts; ++i) {
    pdata->vertices[i] = vertices[i];
  }

  for (i = 0; i < num_coords; ++i) {
    pdata->coords[i] = coords[i];
  }

  return 0;

fail:
  return -1;
}

static int create_programs(limare_data_t *pdata) {
  char tmpbufm[1024]; /* temp buffer for main function */
  char tmpbuf[1024]; /* temp buffer for whole program */

  const char* swz = (pdata->upload_bpp == 4) ? ".bgr" : ".rgb";

  /* Create shader program for regular operation first. */
  pdata->program = limare_program_new(pdata->state);
  if (pdata->program < 0) goto fail;

  snprintf(tmpbufm, 1024, fshader_main_src, swz);
  strncpy(tmpbuf, fshader_header_src, 1024);
  strcat(tmpbuf, tmpbufm);

  if (vertex_shader_attach(pdata->state, pdata->program, vshader_src)) goto fail;
  if (fragment_shader_attach(pdata->state, pdata->program, tmpbuf)) goto fail;
  if (limare_link(pdata->state)) goto fail;

  /* Create shader program for RGUI with RGBA4444 pixel data. */
  pdata->program_rgui_rgba16 = limare_program_new(pdata->state);
  if (pdata->program_rgui_rgba16 < 0) goto fail;

  snprintf(tmpbufm, 1024, fshader_rgui_main_src, ".abgr");
  strncpy(tmpbuf, fshader_rgui_header_src, 1024);
  strcat(tmpbuf, tmpbufm);

  if (vertex_shader_attach(pdata->state, pdata->program_rgui_rgba16, vshader_src)) goto fail;
  if (fragment_shader_attach(pdata->state, pdata->program_rgui_rgba16, tmpbuf)) goto fail;
  if (limare_link(pdata->state)) goto fail;

  /* Create shader program for RGUI with RGBA8888 pixel data. */
  pdata->program_rgui_rgba32 = limare_program_new(pdata->state);
  if (pdata->program_rgui_rgba32 < 0) goto fail;

  snprintf(tmpbufm, 1024, fshader_rgui_main_src, ".abgr");
  strncpy(tmpbuf, fshader_rgui_header_src, 1024);
  strcat(tmpbuf, tmpbufm);

  if (vertex_shader_attach(pdata->state, pdata->program_rgui_rgba32, vshader_src)) goto fail;
  if (fragment_shader_attach(pdata->state, pdata->program_rgui_rgba32, tmpbuf)) goto fail;
  if (limare_link(pdata->state)) goto fail;

  return 0;

fail:
  return -1;
}

static void put_glyph_rgba4444(limare_data_t *pdata, const uint8_t *src, uint8_t *f_rgb,
                               unsigned g_width, unsigned g_height, unsigned g_pitch,
                               unsigned dst_x, unsigned dst_y) {
  unsigned x, y;
  uint16_t *dst;

  dst = (uint16_t*)pdata->buffer + dst_y * pdata->font_width + dst_x;

  for (y = 0; y < g_height; ++y, src += g_pitch, dst += pdata->font_width) {
    for (x = 0; x < g_width; ++x) {
      const uint8_t blend = src[x];

      if (blend != 0) put_pixel_rgba4444(&dst[x], f_rgb[0], f_rgb[1], f_rgb[2], blend);
    }
  }
}

typedef struct lima_video {
  limare_data_t *lima;

  void *font;
  const font_renderer_driver_t *font_driver;
  uint8_t font_rgb[4];

  /* current dimensions */
  unsigned width;
  unsigned height;

  /* RGUI data */
  int rgui_rotation;
  float rgui_alpha;
  bool rgui_active;
  bool rgui_rgb32;

  bool aspect_changed;

} lima_video_t;

static void lima_gfx_free(void *data) {
  lima_video_t *vid = data;
  if (!vid) return;

  if (vid->lima && vid->lima->state) limare_finish(vid->lima->state);
  if (vid->font) vid->font_driver->free(vid->font);

  destroy_data(vid->lima);
  destroy_textures(vid->lima);
  free(vid->lima->textures);

  free(vid->lima);
  free(vid);
}

static void lima_init_font(lima_video_t *vid, const char *font_path, unsigned font_size) {
  if (!g_settings.video.font_enable) return;

  if (font_renderer_create_default(&vid->font_driver, &vid->font)) {
    int r = g_settings.video.msg_color_r * 255;
    int g = g_settings.video.msg_color_g * 255;
    int b = g_settings.video.msg_color_b * 255;

    vid->font_rgb[0] = r < 0 ? 0 : (r > 255 ? 255 : r);
    vid->font_rgb[1] = g < 0 ? 0 : (g > 255 ? 255 : g);
    vid->font_rgb[2] = b < 0 ? 0 : (b > 255 ? 255 : b);
  } else {
    RARCH_LOG("video_lima: font init failed\n");
  }
}

static void lima_render_msg(lima_video_t *vid, const char *msg) {
  struct font_output_list out;
  struct font_output *head;

  unsigned req_size;
  limare_data_t *lima = vid->lima;

  const int msg_base_x = g_settings.video.msg_pos_x * lima->font_width;
  const int msg_base_y = (1.0 - g_settings.video.msg_pos_y) * lima->font_height;

  if (vid->font == NULL) return;

  /* Font texture uses RGBA4444 pixel data (2 bytes per pixel). */
  req_size = lima->font_width * lima->font_height * 2;

  if (lima->buffer_size < req_size) {
    const unsigned aligned_size = align_common(req_size, 16);

    free(lima->buffer);
    lima->buffer = NULL;

    posix_memalign(&lima->buffer, 16, aligned_size);
    if (lima->buffer == NULL) {
      RARCH_ERR("video_lima: failed to allocate buffer to render fonts\n");
      return;
    }

    lima->buffer_size = aligned_size;
  }

  memset(lima->buffer, 0, req_size);

  vid->font_driver->render_msg(vid->font, msg, &out);

  for (head = out.head; head; head = head->next) {
    int base_x = msg_base_x + head->off_x;
    int base_y = msg_base_y - head->off_y - head->height;

    const int max_width  = lima->font_width - base_x;
    const int max_height = lima->font_height - base_y;

    int glyph_width  = head->width;
    int glyph_height = head->height;

    const uint8_t *src = head->output;

    if (base_x < 0) {
       src -= base_x;
       glyph_width += base_x;
       base_x = 0;
    }

    if (base_y < 0) {
       src -= base_y * (int)head->pitch;
       glyph_height += base_y;
       base_y = 0;
    }

    if (max_width <= 0 || max_height <= 0) continue;

    if (glyph_width > max_width) glyph_width = max_width;
    if (glyph_height > max_height) glyph_height = max_height;

    put_glyph_rgba4444(lima, src, vid->font_rgb,
                       glyph_width, glyph_height,
                       head->pitch, base_x, base_y);
  }

  vid->font_driver->free_output(vid->font, &out);
}

static void *lima_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data) {
  lima_video_t *vid = NULL;
  limare_data_t *lima = NULL;
  void *lima_input = NULL;
  struct limare_windowsys_drm limare_config = { 0 };

  vid = calloc(1, sizeof(lima_video_t));
  if (!vid) return NULL;

  vid->rgui_alpha = 1.0f;

  lima = calloc(1, sizeof(limare_data_t));
  if (!lima) goto fail;

  /* Request the Exynos DRM backend for rendering. */
  limare_config.type = LIMARE_WINDOWSYS_DRM;
  limare_config.connector_index = g_settings.video.monitor_index;

  lima->state = limare_init(&limare_config);

  if (!lima->state) {
    RARCH_ERR("video_lima: limare initialization failed\n");
    goto fail;
  }

  limare_buffer_clear(lima->state);

  if (limare_state_setup(lima->state, g_settings.video.fullscreen_x,
                         g_settings.video.fullscreen_y, 0xff000000)) {
    RARCH_ERR("video_lima: limare state setup failed\n");
    goto fail_lima;
  }

  lima->screen_aspect = get_screen_aspect(lima->state);

  lima->font_height = 368;
  lima->font_width = align_common((unsigned)(lima->screen_aspect * (float)lima->font_height), 16);

  lima->upload_format = video->rgb32 ?
    LIMA_TEXEL_FORMAT_RGBA_8888 : LIMA_TEXEL_FORMAT_BGR_565;
  lima->upload_bpp = video->rgb32 ? 4 : 2;

  limare_enable(lima->state, GL_DEPTH_TEST);
  limare_depth_func(lima->state, GL_ALWAYS);
  limare_depth_mask(lima->state, GL_TRUE);

  limare_enable(lima->state, GL_CULL_FACE);

  limare_blend_func(lima->state, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  if (setup_data(lima)) {
    RARCH_ERR("video_lima: data setup failed\n");
    goto fail_lima;
  }

  if (create_programs(lima)) {
    RARCH_ERR("video_lima: creating shader programs failed\n");
    goto fail_lima;
  }

  lima->textures = calloc(num_max_textures, sizeof(limare_texture_t*));

  if (input && input_data) {
    *input = NULL;
    *input_data = NULL;
  }

  vid->lima = lima;

  lima_init_font(vid, g_settings.video.font_path, g_settings.video.font_size);

  return vid;

fail_lima:
  limare_finish(lima->state);
fail:
  free(lima);
  free(vid);

  return NULL;
}

static bool lima_gfx_frame(void *data, const void *frame,
                       unsigned width, unsigned height,
                       unsigned pitch, const char *msg) {
  lima_video_t *vid;
  const void *pixels;
  limare_data_t *lima;
  bool upload_frame = true;

  vid = data;

  /* Check if neither RGUI nor emulator framebuffer is to be displayed. */
  if (!vid->rgui_active && frame == NULL) return true;

  lima = vid->lima;

  if (frame != NULL) {

    /* Handle resolution changes from the emulation core. */
    if (width != vid->width || height != vid->height) {
      limare_texture_t *tex;

      if (width == 0 || height == 0) return true;

      RARCH_LOG("video_lima: resolution was changed by core to %ux%u\n", width, height);
      tex = get_texture_handle(lima, width, height, 0);

      if (tex == NULL) {
        pixels = make_contiguous(lima, width, height, frame, 0, pitch);

        tex = add_texture(lima, width, height, pixels, 0);

        if (tex == NULL) {
          RARCH_ERR("video_lima: failed to allocate new texture with dimensions %ux%u\n",
            width, height);
          return false;
        }

        upload_frame = false; /* pixel data already got uploaded during texture allocation */
      }

      lima->cur_texture = tex;

      vid->width = width;
      vid->height = height;

      lima->frame_aspect = (float)width / (float)height;
      vid->aspect_changed = true;
    }

    if (upload_frame) {
      pixels = make_contiguous(lima, width, height, frame, 0, pitch);
      limare_texture_mipmap_upload(lima->state, lima->cur_texture->handle, 0, pixels);
    }
  }

  if (g_settings.fps_show) {
    char buffer[128], buffer_fps[128];

    gfx_get_fps(buffer, sizeof(buffer), g_settings.fps_show ? buffer_fps : NULL, sizeof(buffer_fps));
    msg_queue_push(g_extern.msg_queue, buffer_fps, 1, 1);
  }

  if (vid->aspect_changed) {
    apply_aspect(lima, g_extern.system.aspect_ratio);
    vid->aspect_changed = false;
  }

  limare_frame_new(lima->state);

  if (lima->cur_texture != NULL) {
    limare_program_current(lima->state, lima->program);

    limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT,
				 3, 0, 4, lima->vertices);
    limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT,
				 2, 0, 4, lima->coords + vid->rgui_rotation * 4);

    limare_texture_attach(lima->state, "in_texture", lima->cur_texture->handle);

    if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false;
  }

  /* Handle font rendering. */
  if (msg) {
    bool upload_font = true;

    /* Both font_vertices and font_color are constant, but we can't make them   *
     * const, since limare_attribute_pointer expects (non-const) void pointers. */
    static vec3f_t font_vertices[4] = {
      {-1.0f, -1.0f,  0.0f},
      { 1.0f, -1.0f,  0.0f},
      {-1.0f,  1.0f,  0.0f},
      { 1.0f,  1.0f,  0.0f}
    };
    static float font_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};

    lima_render_msg(vid, msg);

    if (lima->font_texture == NULL) {
      lima->font_texture = add_texture(lima, lima->font_width, lima->font_height,
                                       lima->buffer, LIMA_TEXEL_FORMAT_RGBA_4444);
      upload_font = false;
    }

    if (upload_font)
      limare_texture_mipmap_upload(lima->state, lima->font_texture->handle, 0, lima->buffer);

    /* We re-use the RGBA16 RGUI program here. */
    limare_program_current(lima->state, lima->program_rgui_rgba16);

    limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT,
				 3, 0, 4, font_vertices);
    limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT,
				 2, 0, 4, lima->coords + vid->rgui_rotation * 4);

    limare_texture_attach(lima->state, "in_texture", lima->font_texture->handle);
    limare_uniform_attach(lima->state, "uColor", 4, font_color);

    limare_enable(lima->state, GL_BLEND);
      if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false;
    limare_disable(lima->state, GL_BLEND);
  }

  if (vid->rgui_active && lima->cur_texture_rgui != NULL) {
    float color[4] = {1.0f, 1.0f, 1.0f, vid->rgui_alpha};

    if (vid->rgui_rgb32)
      limare_program_current(lima->state, lima->program_rgui_rgba32);
    else
      limare_program_current(lima->state, lima->program_rgui_rgba16);

    limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT,
				 3, 0, 4, lima->vertices);
    limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT,
				 2, 0, 4, lima->coords + vid->rgui_rotation * 4);

    limare_texture_attach(lima->state, "in_texture", lima->cur_texture_rgui->handle);
    limare_uniform_attach(lima->state, "uColor", 4, color);

    limare_enable(lima->state, GL_BLEND);
      if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false;
    limare_disable(lima->state, GL_BLEND);
  }

  if (limare_frame_flush(lima->state)) return false;

  limare_buffer_swap(lima->state);

  g_extern.frame_count++;

#ifdef LIMA_GFX_DEBUG
  print_status(lima);
#endif

  return true;
}

static void lima_gfx_set_nonblock_state(void *data, bool state) {
  (void)data; /* limare doesn't export vsync control yet */
  (void)state;
}

static bool lima_gfx_alive(void *data) {
  (void)data;
  return true; /* always alive */
}

static bool lima_gfx_focus(void *data) {
  (void)data;
  return true; /* limare doesn't use windowing, so we always have focus */
}

static void lima_gfx_set_rotation(void *data, unsigned rotation) {
  lima_video_t *vid = data;

  vid->rgui_rotation = rotation;
}

static void lima_gfx_viewport_info(void *data, struct rarch_viewport *vp) {
  lima_video_t *vid = data;
  vp->x = vp->y = 0;

  vp->width  = vp->full_width  = vid->width;
  vp->height = vp->full_height = vid->height;
}

static void lima_set_aspect_ratio(void *data, unsigned aspect_ratio_idx) {
  lima_video_t *vid = data;

  switch (aspect_ratio_idx) {
    case ASPECT_RATIO_SQUARE:
      gfx_set_square_pixel_viewport(g_extern.system.av_info.geometry.base_width, g_extern.system.av_info.geometry.base_height);
    break;

    case ASPECT_RATIO_CORE:
      gfx_set_core_viewport();
    break;

    case ASPECT_RATIO_CONFIG:
      gfx_set_config_viewport();
    break;

    default:
    break;
  }

  g_extern.system.aspect_ratio = aspectratio_lut[aspect_ratio_idx].value;
  vid->aspect_changed = true;
}

static void lima_apply_state_changes(void *data) {
  (void)data;
}

static void lima_set_texture_frame(void *data, const void *frame, bool rgb32,
                               unsigned width, unsigned height, float alpha) {
  lima_video_t *vid = data;
  limare_texture_t* tex;
  const unsigned format = rgb32 ? LIMA_TEXEL_FORMAT_RGBA_8888 :
                                  LIMA_TEXEL_FORMAT_RGBA_4444;

  vid->rgui_rgb32 = rgb32;
  vid->rgui_alpha = alpha;

  tex = vid->lima->cur_texture_rgui;

  /* Current RGUI doesn't change dimensions, so we should hit this most of the time. */
  if (tex != NULL && tex->width == width &&
      tex->height == height && tex->format == format) goto upload;

  if (tex == NULL) {
    tex = get_texture_handle(vid->lima, width, height, format);
    if (tex == NULL) {
      tex = add_texture(vid->lima, width, height, frame, format);

      if (tex != NULL) {
        vid->lima->cur_texture_rgui = tex;
        goto upload;
      }

      RARCH_ERR("video_lima: failed to allocate new RGUI texture with dimensions %ux%u\n",
            width, height);
    }
  }

  return;

upload:
  limare_texture_mipmap_upload(vid->lima->state, tex->handle, 0, frame);
}

static void lima_set_texture_enable(void *data, bool state, bool full_screen) {
  lima_video_t *vid = data;
  vid->rgui_active = state;
}

static void lima_set_osd_msg(void *data, const char *msg, void *userdata) {
  lima_video_t *vid = data;

  /* TODO: what does this do? */
  (void)msg;
  (void)userdata;
}

static void lima_show_mouse(void *data, bool state) {
  (void)data;
}

static const video_poke_interface_t lima_poke_interface = {
  NULL, /* set_filtering */
#ifdef HAVE_FBO
  NULL, /* get_current_framebuffer */
  NULL, /* get_proc_address */
#endif
  lima_set_aspect_ratio,
  lima_apply_state_changes,
#if defined(HAVE_RGUI) || defined(HAVE_RMENU) /* TODO: only HAVE_MENU i think */
  lima_set_texture_frame,
  lima_set_texture_enable,
#endif
  lima_set_osd_msg,
  lima_show_mouse
};

static void lima_gfx_get_poke_interface(void *data, const video_poke_interface_t **iface) {
  (void)data;
  *iface = &lima_poke_interface;
}

const video_driver_t video_lima = {
  lima_gfx_init,
  lima_gfx_frame,
  lima_gfx_set_nonblock_state,
  lima_gfx_alive,
  lima_gfx_focus,
  NULL, /* set_shader */
  lima_gfx_free,
  "lima",

#ifdef HAVE_MENU
  NULL, /* restart */
#endif

  lima_gfx_set_rotation,
  lima_gfx_viewport_info,
  NULL, /* read_viewport */

#ifdef HAVE_OVERLAY
  NULL, /* overlay_interface */
#endif
  lima_gfx_get_poke_interface
};