/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 * 
 *  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/>.
 */

#ifdef _MSC_VER
#pragma comment(lib, "cg")
#pragma comment(lib, "cggl")
#endif

#include <stdint.h>
#include <string.h>

#include <compat/strl.h>
#include <compat/posix_string.h>
#include <file/config_file.h>
#include <file/file_path.h>
#include <retro_assert.h>
#include <rhash.h>
#include <string/stdstring.h>

#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif

#ifdef HAVE_OPENGL
#include "../common/gl_common.h"
#include "../include/Cg/cgGL.h"
#endif

#ifdef HAVE_SHADERPIPELINE
#include "../drivers/gl_shaders/pipeline_xmb_ribbon_simple.cg.h"
#include "../drivers/gl_shaders/pipeline_snow.cg.h"
#endif

#include "../include/Cg/cg.h"

#include "../video_driver.h"
#include "../video_shader_parse.h"
#include "../../core.h"
#include "../../managers/state_manager.h"

#define SEMANTIC_TEXCOORD     0x92ee91cdU
#define SEMANTIC_TEXCOORD0    0xf0c0cb9dU
#define SEMANTIC_TEXCOORD1    0xf0c0cb9eU
#define SEMANTIC_COLOR        0x0ce809a4U
#define SEMANTIC_COLOR0       0xa9e93e54U
#define SEMANTIC_POSITION     0xd87309baU

#define PREV_TEXTURES         (GFX_MAX_TEXTURES - 1)

#define set_param_2f(param, x, y)    if (param) cgGLSetParameter2f(param, x, y)
#define cg_gl_set_param_1f(param, x) if (param) cgGLSetParameter1f(param, x)

#if 0
#define RARCH_CG_DEBUG
#endif

struct cg_fbo_params
{
   CGparameter vid_size_f;
   CGparameter tex_size_f;
   CGparameter vid_size_v;
   CGparameter tex_size_v;
   CGparameter tex;
   CGparameter coord;
};

struct shader_program_cg
{
   CGprogram vprg;
   CGprogram fprg;

   CGparameter tex;
   CGparameter lut_tex;
   CGparameter color;
   CGparameter vertex;

   CGparameter vid_size_f;
   CGparameter tex_size_f;
   CGparameter out_size_f;
   CGparameter frame_cnt_f;
   CGparameter frame_dir_f;
   CGparameter vid_size_v;
   CGparameter tex_size_v;
   CGparameter out_size_v;
   CGparameter frame_cnt_v;
   CGparameter frame_dir_v;
   CGparameter mvp;

   struct cg_fbo_params fbo[GFX_MAX_SHADERS];
   struct cg_fbo_params orig;
   struct cg_fbo_params feedback;
   struct cg_fbo_params prev[PREV_TEXTURES];
};

typedef struct cg_shader_data
{
   struct video_shader *shader;
   char alias_define[GFX_MAX_SHADERS][128];
   unsigned active_idx;
   struct
   {
      CGparameter elems[32 * PREV_TEXTURES + 2 + 4 + GFX_MAX_SHADERS];
      unsigned index;
   } attribs;

   CGprofile cgVProf;
   CGprofile cgFProf;
   struct shader_program_cg prg[GFX_MAX_SHADERS];
   GLuint lut_textures[GFX_MAX_TEXTURES];
   state_tracker_t *state_tracker;
   CGcontext cgCtx;
} cg_shader_data_t;

struct uniform_cg
{
   CGparameter loc;
};

#define gl_cg_set_coord_array(param, cg, ptr, len) \
{ \
   cgGLSetParameterPointer(param, len, GL_FLOAT, 0, ptr); \
   cgGLEnableClientState(param); \
   cg->attribs.elems[cg->attribs.index++] = param; \
}

#define cg_gl_set_texture_parameter(param, texture) \
   if (param) \
   { \
      cgGLSetTextureParameter(param, texture); \
      cgGLEnableTextureParameter(param); \
   }


#include "../drivers/gl_shaders/opaque.cg.h"

static void gl_cg_set_uniform_parameter(
      void *data,
      struct uniform_info *param,
      void *uniform_data)
{
   CGparameter location;
   cg_shader_data_t *cg        = (cg_shader_data_t*)data;

   if (!param || !param->enabled)
      return;

   if (param->lookup.enable)
   {
      char ident[64];
      CGprogram prog = 0;

      ident[0] = '\0';

      switch (param->lookup.type)
      {
         case SHADER_PROGRAM_VERTEX:
            prog = cg->prg[param->lookup.idx].vprg;
            break;
         case SHADER_PROGRAM_FRAGMENT:
         default:
            prog = cg->prg[param->lookup.idx].fprg;
            break;
      }

      if (param->lookup.add_prefix)
         snprintf(ident, sizeof(ident), "IN.%s", param->lookup.ident);
      location = cgGetNamedParameter(prog, param->lookup.add_prefix ? ident : param->lookup.ident);
   }
   else
   {
      struct uniform_cg *cg_param = (struct uniform_cg*)uniform_data;
      location = cg_param->loc;
   }

   switch (param->type)
   {
      case UNIFORM_1F:
         cgGLSetParameter1f(location, param->result.f.v0);
         break;
      case UNIFORM_2F:
         cgGLSetParameter2f(location, param->result.f.v0, param->result.f.v1);
         break;
      case UNIFORM_3F:
         cgGLSetParameter3f(location, param->result.f.v0, param->result.f.v1,
               param->result.f.v2);
         break;
      case UNIFORM_4F:
         cgGLSetParameter4f(location, param->result.f.v0, param->result.f.v1,
               param->result.f.v2, param->result.f.v3);
         break;
      case UNIFORM_1FV:
         cgGLSetParameter1fv(location, param->result.floatv);
         break;
      case UNIFORM_2FV:
         cgGLSetParameter2fv(location, param->result.floatv);
         break;
      case UNIFORM_3FV:
         cgGLSetParameter3fv(location, param->result.floatv);
         break;
      case UNIFORM_4FV:
         cgGLSetParameter3fv(location, param->result.floatv);
         break;
      case UNIFORM_1I:
         /* Unimplemented - Cg limitation */
         break;
   }
}


#ifdef RARCH_CG_DEBUG
static void cg_error_handler(CGcontext ctx, CGerror error, void *data)
{
   (void)ctx;
   (void)data;

   switch (error)
   {
      case CG_INVALID_PARAM_HANDLE_ERROR:
         RARCH_ERR("CG: Invalid param handle.\n");
         break;

      case CG_INVALID_PARAMETER_ERROR:
         RARCH_ERR("CG: Invalid parameter.\n");
         break;

      default:
         break;
   }

   RARCH_ERR("CG error: \"%s\"\n", cgGetErrorString(error));
}
#endif

static void gl_cg_reset_attrib(void *data)
{
   unsigned i;
   cg_shader_data_t *cg = (cg_shader_data_t*)data;

   /* Add sanity check that we did not overflow. */
   retro_assert(cg->attribs.index <= ARRAY_SIZE(cg->attribs.elems));

   for (i = 0; i < cg->attribs.index; i++)
      cgGLDisableClientState(cg->attribs.elems[i]);
   cg->attribs.index = 0;
}

static bool gl_cg_set_mvp(void *data, void *shader_data, const math_matrix_4x4 *mat)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)shader_data;
   if (!cg || !cg->prg[cg->active_idx].mvp)
   {
      gl_ff_matrix(mat);
      return false;
   }

   cgGLSetMatrixParameterfc(cg->prg[cg->active_idx].mvp, mat->data);
   return true;
}

static bool gl_cg_set_coords(void *handle_data, void *shader_data, const struct video_coords *coords)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)shader_data;

   if (!cg || !coords)
   {
      if (coords)
         return false;
      return true;
   }

   if (cg->prg[cg->active_idx].vertex)
      gl_cg_set_coord_array(cg->prg[cg->active_idx].vertex, cg, coords->vertex, 2);

   if (cg->prg[cg->active_idx].tex)
      gl_cg_set_coord_array(cg->prg[cg->active_idx].tex, cg, coords->tex_coord, 2);

   if (cg->prg[cg->active_idx].lut_tex)
      gl_cg_set_coord_array(cg->prg[cg->active_idx].lut_tex, cg, coords->lut_tex_coord, 2);

   if (cg->prg[cg->active_idx].color)
      gl_cg_set_coord_array(cg->prg[cg->active_idx].color, cg, coords->color, 4);

   return true;
}

static bool gl_cg_set_coords_fallback(void *handle_data, void *shader_data, const struct video_coords *coords)
{
   gl_ff_vertex(coords);
   return true;
}

static void gl_cg_set_texture_info(
      cg_shader_data_t *cg, 
      const struct cg_fbo_params *params,
      const struct video_tex_info *info)
{
   CGparameter param                     = params->tex;

   cg_gl_set_texture_parameter(param, info->tex);

   set_param_2f(params->vid_size_v,
         info->input_size[0], info->input_size[1]);
   set_param_2f(params->vid_size_f,
         info->input_size[0], info->input_size[1]);
   set_param_2f(params->tex_size_v,
         info->tex_size[0],   info->tex_size[1]);
   set_param_2f(params->tex_size_f,
         info->tex_size[0],   info->tex_size[1]);

   if (params->coord)
      gl_cg_set_coord_array(params->coord, cg, info->coord, 2);
}

static void gl_cg_set_params(void *data, void *shader_data,
      unsigned width, unsigned height, 
      unsigned tex_width, unsigned tex_height,
      unsigned out_width, unsigned out_height,
      unsigned frame_count,
      const void *_info,
      const void *_prev_info,
      const void *_feedback_info,
      const void *_fbo_info,
      unsigned fbo_info_cnt)
{
   unsigned i;
   const struct video_tex_info *info          = (const struct video_tex_info*)_info;
   const struct video_tex_info *prev_info     = (const struct video_tex_info*)_prev_info;
   const struct video_tex_info *feedback_info = (const struct video_tex_info*)_feedback_info;
   const struct video_tex_info *fbo_info      = (const struct video_tex_info*)_fbo_info;
   cg_shader_data_t *cg                = (cg_shader_data_t*)shader_data;

   if (!cg || (cg->active_idx == 0))
         return;
   if (cg->active_idx == VIDEO_SHADER_STOCK_BLEND)
      return;

   /* Set frame. */
   set_param_2f(cg->prg[cg->active_idx].vid_size_f, width, height);
   set_param_2f(cg->prg[cg->active_idx].tex_size_f, tex_width, tex_height);
   set_param_2f(cg->prg[cg->active_idx].out_size_f, out_width, out_height);
   cg_gl_set_param_1f(cg->prg[cg->active_idx].frame_dir_f,
         state_manager_frame_is_reversed() ? -1.0 : 1.0);

   set_param_2f(cg->prg[cg->active_idx].vid_size_v, width, height);
   set_param_2f(cg->prg[cg->active_idx].tex_size_v, tex_width, tex_height);
   set_param_2f(cg->prg[cg->active_idx].out_size_v, out_width, out_height);
   cg_gl_set_param_1f(cg->prg[cg->active_idx].frame_dir_v,
         state_manager_frame_is_reversed() ? -1.0 : 1.0);

   if (cg->prg[cg->active_idx].frame_cnt_f || cg->prg[cg->active_idx].frame_cnt_v)
   {
      unsigned modulo = cg->shader->pass[cg->active_idx - 1].frame_count_mod;
      if (modulo)
         frame_count %= modulo;

      cg_gl_set_param_1f(cg->prg[cg->active_idx].frame_cnt_f, (float)frame_count);
      cg_gl_set_param_1f(cg->prg[cg->active_idx].frame_cnt_v, (float)frame_count);
   }

   /* Set lookup textures. */
   for (i = 0; i < cg->shader->luts; i++)
   {
      CGparameter fparam = cgGetNamedParameter(
            cg->prg[cg->active_idx].fprg, cg->shader->lut[i].id);
      CGparameter vparam = cgGetNamedParameter(cg->prg[cg->active_idx].vprg,
		  cg->shader->lut[i].id);

      cg_gl_set_texture_parameter(fparam, cg->lut_textures[i]);
      cg_gl_set_texture_parameter(vparam, cg->lut_textures[i]);
   }

   if (cg->active_idx)
   {
      /* Set original texture. */
      gl_cg_set_texture_info(cg, &cg->prg[cg->active_idx].orig, info);

      /* Set feedback texture. */
      gl_cg_set_texture_info(cg, &cg->prg[cg->active_idx].feedback, feedback_info);

      /* Bind FBO textures. */
      for (i = 0; i < fbo_info_cnt; i++)
         gl_cg_set_texture_info(cg, &cg->prg[cg->active_idx].fbo[i], &fbo_info[i]);
   }

   /* Set previous textures. */
   for (i = 0; i < PREV_TEXTURES; i++)
      gl_cg_set_texture_info(cg, &cg->prg[cg->active_idx].prev[i], &prev_info[i]);

   /* #pragma parameters. */
   for (i = 0; i < cg->shader->num_parameters; i++)
   {
      CGparameter param_v = cgGetNamedParameter(
            cg->prg[cg->active_idx].vprg, cg->shader->parameters[i].id);
      CGparameter param_f = cgGetNamedParameter(
            cg->prg[cg->active_idx].fprg, cg->shader->parameters[i].id);
      cg_gl_set_param_1f(param_v, cg->shader->parameters[i].current);
      cg_gl_set_param_1f(param_f, cg->shader->parameters[i].current);
   }

   /* Set state parameters. */
   if (cg->state_tracker)
   {
      /* Only query uniforms in first pass. */
      static struct state_tracker_uniform tracker_info[GFX_MAX_VARIABLES];
      static unsigned cnt = 0;

      if (cg->active_idx == 1)
         cnt = state_tracker_get_uniform(cg->state_tracker, tracker_info,
               GFX_MAX_VARIABLES, frame_count);

      for (i = 0; i < cnt; i++)
      {
         CGparameter param_v = cgGetNamedParameter(
               cg->prg[cg->active_idx].vprg, tracker_info[i].id);
         CGparameter param_f = cgGetNamedParameter(
               cg->prg[cg->active_idx].fprg, tracker_info[i].id);
         cg_gl_set_param_1f(param_v, tracker_info[i].value);
         cg_gl_set_param_1f(param_f, tracker_info[i].value);
      }
   }
}

static void gl_cg_deinit_progs(void *data)
{
   unsigned i;
   cg_shader_data_t *cg = (cg_shader_data_t*)data;

   if (!cg)
      return;

   RARCH_LOG("[CG]: Destroying programs.\n");
   cgGLUnbindProgram(cg->cgFProf);
   cgGLUnbindProgram(cg->cgVProf);

   /* Programs may alias [0]. */
   for (i = 1; i < GFX_MAX_SHADERS; i++)
   {
      if (cg->prg[i].fprg && cg->prg[i].fprg != cg->prg[0].fprg)
         cgDestroyProgram(cg->prg[i].fprg);
      if (cg->prg[i].vprg && cg->prg[i].vprg != cg->prg[0].vprg)
         cgDestroyProgram(cg->prg[i].vprg);
   }

   if (cg->prg[0].fprg)
      cgDestroyProgram(cg->prg[0].fprg);
   if (cg->prg[0].vprg)
      cgDestroyProgram(cg->prg[0].vprg);

   memset(cg->prg, 0, sizeof(cg->prg));
}

static void gl_cg_destroy_resources(void *data)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (!cg)
      return;

   gl_cg_reset_attrib(data);

   gl_cg_deinit_progs(data);

   if (cg->shader && cg->shader->luts)
   {
      glDeleteTextures(cg->shader->luts, cg->lut_textures);
      memset(cg->lut_textures, 0, sizeof(cg->lut_textures));
   }

   if (cg->state_tracker)
   {
      state_tracker_free(cg->state_tracker);
      cg->state_tracker = NULL;
   }

   free(cg->shader);
   cg->shader = NULL;
}

/* Final deinit. */
static void gl_cg_deinit_context_state(void *data)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (cg->cgCtx)
   {
      RARCH_LOG("[CG]: Destroying context.\n");
      cgDestroyContext(cg->cgCtx);
   }
   cg->cgCtx = NULL;
}

/* Full deinit. */
static void gl_cg_deinit(void *data)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (!cg)
      return;

   gl_cg_destroy_resources(cg);
   gl_cg_deinit_context_state(cg);

   free(cg);
}

static bool gl_cg_compile_program(
      void *data,
      unsigned idx,
      void *program_data,
      struct shader_program_info *program_info)
{
   const char *argv[2 + GFX_MAX_SHADERS];
   const char *list                  = NULL;
   bool ret                          = true;
   char *listing_f                   = NULL;
   char *listing_v                   = NULL;
   unsigned i, argc                  = 0;
   struct shader_program_cg *program = (struct shader_program_cg*)program_data;
   cg_shader_data_t              *cg = (cg_shader_data_t*)data;

   if (!program)
      program = &cg->prg[idx];

   argv[argc++] = "-DPARAMETER_UNIFORM";

   for (i = 0; i < GFX_MAX_SHADERS; i++)
   {
      if (*(cg->alias_define[i]))
         argv[argc++] = cg->alias_define[i];
   }

   argv[argc] = NULL;

   if (program_info->is_file)
      program->fprg = cgCreateProgramFromFile(
            cg->cgCtx, CG_SOURCE,
            program_info->combined, cg->cgFProf, "main_fragment", argv);
   else
      program->fprg = cgCreateProgram(cg->cgCtx, CG_SOURCE,
            program_info->combined, cg->cgFProf, "main_fragment", argv);

   list = cgGetLastListing(cg->cgCtx);

   if (list)
      listing_f = strdup(list);

   list = NULL;

   if (program_info->is_file)
      program->vprg = cgCreateProgramFromFile(
            cg->cgCtx, CG_SOURCE,
            program_info->combined, cg->cgVProf, "main_vertex", argv);
   else
      program->vprg = cgCreateProgram(cg->cgCtx, CG_SOURCE,
            program_info->combined, cg->cgVProf, "main_vertex", argv);

   list = cgGetLastListing(cg->cgCtx);

   if (list)
      listing_v = strdup(list);

   if (!program->fprg || !program->vprg)
   {
      RARCH_ERR("CG error: %s\n", cgGetErrorString(cgGetError()));
      if (listing_f)
         RARCH_ERR("Fragment:\n%s\n", listing_f);
      else if (listing_v)
         RARCH_ERR("Vertex:\n%s\n", listing_v);

      ret = false;
      goto end;
   }

   cgGLLoadProgram(program->fprg);
   cgGLLoadProgram(program->vprg);

end:
   free(listing_f);
   free(listing_v);
   return ret;
}

static void gl_cg_set_program_base_attrib(void *data, unsigned i)
{
   cg_shader_data_t      *cg = (cg_shader_data_t*)data;
   CGparameter         param = cgGetFirstParameter(
         cg->prg[i].vprg, CG_PROGRAM);

   for (; param; param = cgGetNextParameter(param))
   {
      uint32_t semantic_hash;
      const char *semantic = NULL;
      if (     (cgGetParameterDirection(param)   != CG_IN)
            || (cgGetParameterVariability(param) != CG_VARYING))
         continue;

      semantic = cgGetParameterSemantic(param);
      if (!semantic)
         continue;

      RARCH_LOG("[CG]: Found semantic \"%s\" in prog #%u.\n", semantic, i);

      semantic_hash = djb2_calculate(semantic);

      switch (semantic_hash)
      {
         case SEMANTIC_TEXCOORD:
         case SEMANTIC_TEXCOORD0:
            cg->prg[i].tex     = param;
            break;
         case SEMANTIC_COLOR:
         case SEMANTIC_COLOR0:
            cg->prg[i].color   = param;
            break;
         case SEMANTIC_POSITION:
            cg->prg[i].vertex  = param;
            break;
         case SEMANTIC_TEXCOORD1:
            cg->prg[i].lut_tex = param;
            break;
      }
   }

   if (!cg->prg[i].tex)
      cg->prg[i].tex     = cgGetNamedParameter(cg->prg[i].vprg, "IN.tex_coord");
   if (!cg->prg[i].color)
      cg->prg[i].color   = cgGetNamedParameter(cg->prg[i].vprg, "IN.color");
   if (!cg->prg[i].vertex)
      cg->prg[i].vertex  = cgGetNamedParameter(cg->prg[i].vprg, "IN.vertex_coord");
   if (!cg->prg[i].lut_tex)
      cg->prg[i].lut_tex = cgGetNamedParameter(cg->prg[i].vprg, "IN.lut_tex_coord");
}

static bool gl_cg_load_stock(void *data)
{
   struct shader_program_info program_info;
   cg_shader_data_t *cg  = (cg_shader_data_t*)data;

   program_info.combined = stock_cg_gl_program;
   program_info.is_file  = false;

   if (!gl_cg_compile_program(data, 0, &cg->prg[0], &program_info))
      goto error;

   gl_cg_set_program_base_attrib(data, 0);

   return true;

error:
   RARCH_ERR("Failed to compile passthrough shader, is something wrong with your environment?\n");
   return false;
}

static bool gl_cg_load_plain(void *data, const char *path)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;

   if (!gl_cg_load_stock(cg))
      return false;

   cg->shader = (struct video_shader*)
      calloc(1, sizeof(*cg->shader));
   if (!cg->shader)
      return false;

   cg->shader->passes = 1;

   if (string_is_empty(path))
   {
      RARCH_LOG("[CG]: Loading stock Cg file.\n");
      cg->prg[1] = cg->prg[0];
   }
   else
   {
      struct shader_program_info program_info;

      program_info.combined = path;
      program_info.is_file  = true;

      RARCH_LOG("[CG]: Loading Cg file: %s\n", path);
      strlcpy(cg->shader->pass[0].source.path, path,
            sizeof(cg->shader->pass[0].source.path));
      if (!gl_cg_compile_program(data, 1, &cg->prg[1], &program_info))
         return false;
   }

   video_shader_resolve_parameters(NULL, cg->shader);
   return true;
}

static bool gl_cg_load_imports(void *data)
{
   unsigned i;
   retro_ctx_memory_info_t mem_info;
   struct state_tracker_info tracker_info;
   cg_shader_data_t                   *cg = (cg_shader_data_t*)data;

   if (!cg->shader->variables)
      return true;

   for (i = 0; i < cg->shader->variables; i++)
   {
      unsigned memtype;

      switch (cg->shader->variable[i].ram_type)
      {
         case RARCH_STATE_WRAM:
            memtype = RETRO_MEMORY_SYSTEM_RAM;
            break;

         default:
            memtype = -1u;
      }

      mem_info.id = memtype;

      core_get_memory(&mem_info);

      if ((memtype != -1u) && 
            (cg->shader->variable[i].addr >= mem_info.size))
      {
         RARCH_ERR("Address out of bounds.\n");
         return false;
      }
   }

   mem_info.data                  = NULL;
   mem_info.size                  = 0;
   mem_info.id                    = RETRO_MEMORY_SYSTEM_RAM;

   core_get_memory(&mem_info);

   tracker_info.wram              = (uint8_t*)mem_info.data;
   tracker_info.info              = cg->shader->variable;
   tracker_info.info_elem         = cg->shader->variables;
   tracker_info.script            = NULL;
   tracker_info.script_is_file    = false;

#ifdef HAVE_PYTHON
   if (*cg->shader->script_path)
   {
      tracker_info.script         = cg->shader->script_path;
      tracker_info.script_is_file = true;
   }

   tracker_info.script_class = 
      *cg->shader->script_class ? cg->shader->script_class : NULL;
#endif

   cg->state_tracker = state_tracker_init(&tracker_info);
   if (!cg->state_tracker)
      RARCH_WARN("Failed to initialize state tracker.\n");

   return true;
}

static bool gl_cg_load_shader(void *data, unsigned i)
{
   struct shader_program_info program_info;
   cg_shader_data_t *cg  = (cg_shader_data_t*)data;

   program_info.combined = cg->shader->pass[i].source.path;
   program_info.is_file  = true;

   RARCH_LOG("[CG]: Loading Cg shader: \"%s\".\n",
         cg->shader->pass[i].source.path);

   if (!gl_cg_compile_program(data, i + 1, &cg->prg[i + 1],&program_info))
      return false;

   return true;
}

static bool gl_cg_load_preset(void *data, const char *path)
{
   unsigned i;
   config_file_t  *conf = NULL;
   cg_shader_data_t *cg = (cg_shader_data_t*)data;

   if (!gl_cg_load_stock(cg))
      return false;

   RARCH_LOG("[CG]: Loading Cg meta-shader: %s\n", path);
   conf = config_file_new(path);
   if (!conf)
   {
      RARCH_ERR("Failed to load preset.\n");
      return false;
   }

   cg->shader = (struct video_shader*)calloc(1, sizeof(*cg->shader));
   if (!cg->shader)
   {
      config_file_free(conf);
      return false;
   }

   if (!video_shader_read_conf_cgp(conf, cg->shader))
   {
      RARCH_ERR("Failed to parse CGP file.\n");
      config_file_free(conf);
      return false;
   }

   video_shader_resolve_relative(cg->shader, path);
   video_shader_resolve_parameters(conf, cg->shader);
   config_file_free(conf);

   if (cg->shader->passes > GFX_MAX_SHADERS - 3)
   {
      RARCH_WARN("Too many shaders ... Capping shader amount to %d.\n",
            GFX_MAX_SHADERS - 3);
      cg->shader->passes = GFX_MAX_SHADERS - 3;
   }

   for (i = 0; i < cg->shader->passes; i++)
   {
      if (*cg->shader->pass[i].alias)
         snprintf(cg->alias_define[i],
               sizeof(cg->alias_define[i]),
               "-D%s_ALIAS",
               cg->shader->pass[i].alias);
   }

   for (i = 0; i < cg->shader->passes; i++)
   {
      if (!gl_cg_load_shader(cg, i))
      {
         RARCH_ERR("Failed to load shaders ...\n");
         return false;
      }
   }

   if (!gl_load_luts(cg->shader, cg->lut_textures))
   {
      RARCH_ERR("Failed to load lookup textures ...\n");
      return false;
   }

   if (!gl_cg_load_imports(cg))
   {
      RARCH_ERR("Failed to load imports ...\n");
      return false;
   }

   return true;
}

static void gl_cg_set_pass_attrib(
      struct shader_program_cg *program,
      struct cg_fbo_params *fbo,
      const char *attr)
{
   char attr_buf[64];

   attr_buf[0] = '\0';

   snprintf(attr_buf, sizeof(attr_buf), "%s.texture", attr);
   if (!fbo->tex)
      fbo->tex = cgGetNamedParameter(program->fprg, attr_buf);

   snprintf(attr_buf, sizeof(attr_buf), "%s.video_size", attr);
   if (!fbo->vid_size_v)
      fbo->vid_size_v = cgGetNamedParameter(program->vprg, attr_buf);
   if (!fbo->vid_size_f)
      fbo->vid_size_f = cgGetNamedParameter(program->fprg, attr_buf);

   snprintf(attr_buf, sizeof(attr_buf), "%s.texture_size", attr);
   if (!fbo->tex_size_v)
      fbo->tex_size_v = cgGetNamedParameter(program->vprg, attr_buf);
   if (!fbo->tex_size_f)
      fbo->tex_size_f = cgGetNamedParameter(program->fprg, attr_buf);

   snprintf(attr_buf, sizeof(attr_buf), "%s.tex_coord", attr);
   if (!fbo->coord)
      fbo->coord = cgGetNamedParameter(program->vprg, attr_buf);
}

static INLINE void gl_cg_set_shaders(CGprogram frag, CGprogram vert)
{
   cgGLBindProgram(frag);
   cgGLBindProgram(vert);
}

static void gl_cg_set_program_attributes(void *data, unsigned i)
{
   unsigned j;
   cg_shader_data_t *cg = (cg_shader_data_t*)data;

   if (!cg)
      return;

   gl_cg_set_shaders(cg->prg[i].fprg, cg->prg[i].vprg);

   gl_cg_set_program_base_attrib(cg, i);

   cg->prg[i].vid_size_f = cgGetNamedParameter (cg->prg[i].fprg, "IN.video_size");
   cg->prg[i].tex_size_f = cgGetNamedParameter (cg->prg[i].fprg, "IN.texture_size");
   cg->prg[i].out_size_f = cgGetNamedParameter (cg->prg[i].fprg, "IN.output_size");
   cg->prg[i].frame_cnt_f = cgGetNamedParameter(cg->prg[i].fprg, "IN.frame_count");
   cg->prg[i].frame_dir_f = cgGetNamedParameter(cg->prg[i].fprg, "IN.frame_direction");
   cg->prg[i].vid_size_v = cgGetNamedParameter (cg->prg[i].vprg, "IN.video_size");
   cg->prg[i].tex_size_v = cgGetNamedParameter (cg->prg[i].vprg, "IN.texture_size");
   cg->prg[i].out_size_v = cgGetNamedParameter (cg->prg[i].vprg, "IN.output_size");
   cg->prg[i].frame_cnt_v = cgGetNamedParameter(cg->prg[i].vprg, "IN.frame_count");
   cg->prg[i].frame_dir_v = cgGetNamedParameter(cg->prg[i].vprg, "IN.frame_direction");

   cg->prg[i].mvp                 = cgGetNamedParameter(cg->prg[i].vprg, "modelViewProj");
   if (!cg->prg[i].mvp)
      cg->prg[i].mvp              = cgGetNamedParameter(cg->prg[i].vprg, "IN.mvp_matrix");

   cg->prg[i].orig.tex            = cgGetNamedParameter(cg->prg[i].fprg, "ORIG.texture");
   cg->prg[i].orig.vid_size_v     = cgGetNamedParameter(cg->prg[i].vprg, "ORIG.video_size");
   cg->prg[i].orig.vid_size_f     = cgGetNamedParameter(cg->prg[i].fprg, "ORIG.video_size");
   cg->prg[i].orig.tex_size_v     = cgGetNamedParameter(cg->prg[i].vprg, "ORIG.texture_size");
   cg->prg[i].orig.tex_size_f     = cgGetNamedParameter(cg->prg[i].fprg, "ORIG.texture_size");
   cg->prg[i].orig.coord          = cgGetNamedParameter(cg->prg[i].vprg, "ORIG.tex_coord");

   cg->prg[i].feedback.tex        = cgGetNamedParameter(cg->prg[i].fprg, "FEEDBACK.texture");
   cg->prg[i].feedback.vid_size_v = cgGetNamedParameter(cg->prg[i].vprg, "FEEDBACK.video_size");
   cg->prg[i].feedback.vid_size_f = cgGetNamedParameter(cg->prg[i].fprg, "FEEDBACK.video_size");
   cg->prg[i].feedback.tex_size_v = cgGetNamedParameter(cg->prg[i].vprg, "FEEDBACK.texture_size");
   cg->prg[i].feedback.tex_size_f = cgGetNamedParameter(cg->prg[i].fprg, "FEEDBACK.texture_size");
   cg->prg[i].feedback.coord      = cgGetNamedParameter(cg->prg[i].vprg, "FEEDBACK.tex_coord");

   if (i > 1)
   {
      char pass_str[64];

      pass_str[0] = '\0';

      snprintf(pass_str, sizeof(pass_str), "PASSPREV%u", i);
      gl_cg_set_pass_attrib(&cg->prg[i], &cg->prg[i].orig, pass_str);
   }

   for (j = 0; j < PREV_TEXTURES; j++)
   {
      char attr_buf_tex[64];
      char attr_buf_vid_size[64];
      char attr_buf_tex_size[64];
      char attr_buf_coord[64];
      static const char *prev_names[PREV_TEXTURES] = {
         "PREV",
         "PREV1",
         "PREV2",
         "PREV3",
         "PREV4",
         "PREV5",
         "PREV6",
      };

      attr_buf_tex[0] = attr_buf_vid_size[0] = attr_buf_tex_size[0] =
         attr_buf_coord[0] = '\0';

      snprintf(attr_buf_tex,      sizeof(attr_buf_tex),     
            "%s.texture", prev_names[j]);
      snprintf(attr_buf_vid_size, sizeof(attr_buf_vid_size),
            "%s.video_size", prev_names[j]);
      snprintf(attr_buf_tex_size, sizeof(attr_buf_tex_size),
            "%s.texture_size", prev_names[j]);
      snprintf(attr_buf_coord,    sizeof(attr_buf_coord),
            "%s.tex_coord", prev_names[j]);

      cg->prg[i].prev[j].tex = cgGetNamedParameter(cg->prg[i].fprg,
            attr_buf_tex);

      cg->prg[i].prev[j].vid_size_v = 
         cgGetNamedParameter(cg->prg[i].vprg, attr_buf_vid_size);
      cg->prg[i].prev[j].vid_size_f = 
         cgGetNamedParameter(cg->prg[i].fprg, attr_buf_vid_size);

      cg->prg[i].prev[j].tex_size_v = 
         cgGetNamedParameter(cg->prg[i].vprg, attr_buf_tex_size);
      cg->prg[i].prev[j].tex_size_f = 
         cgGetNamedParameter(cg->prg[i].fprg, attr_buf_tex_size);

      cg->prg[i].prev[j].coord = cgGetNamedParameter(cg->prg[i].vprg,
            attr_buf_coord);
   }

   for (j = 0; j + 1 < i; j++)
   {
      char pass_str[64];

      pass_str[0] = '\0';

      snprintf(pass_str, sizeof(pass_str), "PASS%u", j + 1);
      gl_cg_set_pass_attrib(&cg->prg[i], &cg->prg[i].fbo[j], pass_str);
      snprintf(pass_str, sizeof(pass_str), "PASSPREV%u", i - (j + 1));
      gl_cg_set_pass_attrib(&cg->prg[i], &cg->prg[i].fbo[j], pass_str);

      if (*cg->shader->pass[j].alias)
         gl_cg_set_pass_attrib(&cg->prg[i], &cg->prg[i].fbo[j],
               cg->shader->pass[j].alias);
   }
}

static void *gl_cg_init(void *data, const char *path)
{
   unsigned i;
   struct shader_program_info shader_prog_info;
   cg_shader_data_t *cg = (cg_shader_data_t*)
      calloc(1, sizeof(cg_shader_data_t));

   if (!cg)
      return NULL;

#ifdef HAVE_CG_RUNTIME_COMPILER
   cgRTCgcInit();
#endif

   cg->cgCtx = cgCreateContext();

   if (!cg->cgCtx)
   {
      RARCH_ERR("Failed to create Cg context.\n");
      goto error;
   }

#ifdef RARCH_CG_DEBUG
   cgGLSetDebugMode(CG_TRUE);
   cgSetErrorHandler(cg_error_handler, NULL);
#endif

   cg->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
   cg->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);

   if (
         cg->cgFProf == CG_PROFILE_UNKNOWN ||
         cg->cgVProf == CG_PROFILE_UNKNOWN)
   {
      RARCH_ERR("Invalid profile type\n");
      goto error;
   }

   RARCH_LOG("[CG]: Vertex profile: %s\n",   cgGetProfileString(cg->cgVProf));
   RARCH_LOG("[CG]: Fragment profile: %s\n", cgGetProfileString(cg->cgFProf));
   cgGLSetOptimalOptions(cg->cgFProf);
   cgGLSetOptimalOptions(cg->cgVProf);
   cgGLEnableProfile(cg->cgFProf);
   cgGLEnableProfile(cg->cgVProf);

   memset(cg->alias_define, 0, sizeof(cg->alias_define));

   if (    !string_is_empty(path) 
         && string_is_equal_fast(path_get_extension(path), "cgp", 3))
   {
      if (!gl_cg_load_preset(cg, path))
         goto error;
   }
   else
   {
      if (!gl_cg_load_plain(cg, path))
         goto error;
   }

   cg->prg[0].mvp = cgGetNamedParameter(cg->prg[0].vprg, "IN.mvp_matrix");

   for (i = 1; i <= cg->shader->passes; i++)
      gl_cg_set_program_attributes(cg, i);

   /* If we aren't using last pass non-FBO shader, 
    * this shader will be assumed to be "fixed-function".
    *
    * Just use prg[0] for that pass, which will be
    * pass-through. */
   cg->prg[cg->shader->passes + 1] = cg->prg[0]; 

   /* No need to apply Android hack in Cg. */
   cg->prg[VIDEO_SHADER_STOCK_BLEND]    = cg->prg[0];

   gl_cg_set_shaders(cg->prg[1].fprg, cg->prg[1].vprg);

#ifdef HAVE_SHADERPIPELINE
   shader_prog_info.combined = stock_xmb_ribbon_simple;
   shader_prog_info.is_file  = false;

   gl_cg_compile_program(
         cg,
         VIDEO_SHADER_MENU,
         &cg->prg[VIDEO_SHADER_MENU],
         &shader_prog_info);
   gl_cg_set_program_base_attrib(cg, VIDEO_SHADER_MENU);

   shader_prog_info.combined = stock_xmb_ribbon_simple;
   shader_prog_info.is_file  = false;

   gl_cg_compile_program(
         cg,
         VIDEO_SHADER_MENU_2,
         &cg->prg[VIDEO_SHADER_MENU_2],
         &shader_prog_info);
   gl_cg_set_program_base_attrib(cg, VIDEO_SHADER_MENU_2);

   shader_prog_info.combined = stock_xmb_snow;
   shader_prog_info.is_file  = false;

   gl_cg_compile_program(
         cg,
         VIDEO_SHADER_MENU_3,
         &cg->prg[VIDEO_SHADER_MENU_3],
         &shader_prog_info);
   gl_cg_set_program_base_attrib(cg, VIDEO_SHADER_MENU_3);
#endif

   gl_cg_reset_attrib(cg);

   return cg;

error:
   gl_cg_destroy_resources(cg);
   if (!cg)
      free(cg);
   return NULL;
}

static void gl_cg_use(void *data, void *shader_data, unsigned idx, bool set_active)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)shader_data;
   if (cg && cg->prg[idx].vprg && cg->prg[idx].fprg)
   {
      if (set_active)
      {
         gl_cg_reset_attrib(cg);
         cg->active_idx = idx;
      }

      gl_cg_set_shaders(cg->prg[idx].fprg, cg->prg[idx].vprg);
   }
}

static unsigned gl_cg_num(void *data)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (!cg)
      return 0;
   return cg->shader->passes;
}

static bool gl_cg_filter_type(void *data, unsigned idx, bool *smooth)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (cg && idx &&
         (cg->shader->pass[idx - 1].filter != RARCH_FILTER_UNSPEC)
      )
   {
      *smooth = (cg->shader->pass[idx - 1].filter == RARCH_FILTER_LINEAR);
      return true;
   }

   return false;
}

static enum gfx_wrap_type gl_cg_wrap_type(void *data, unsigned idx)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (cg && idx)
      return cg->shader->pass[idx - 1].wrap;
   return RARCH_WRAP_BORDER;
}

static void gl_cg_shader_scale(void *data, unsigned idx, struct gfx_fbo_scale *scale)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (cg && idx)
      *scale = cg->shader->pass[idx - 1].fbo;
   else
      scale->valid = false;
}

static unsigned gl_cg_get_prev_textures(void *data)
{
   unsigned i, j;
   unsigned max_prev = 0;
   cg_shader_data_t *cg = (cg_shader_data_t*)data;

   if (!cg)
      return 0;

   for (i = 1; i <= cg->shader->passes; i++)
      for (j = 0; j < PREV_TEXTURES; j++)
         if (cg->prg[i].prev[j].tex)
            max_prev = MAX(j + 1, max_prev);

   return max_prev;
}

static bool gl_cg_get_feedback_pass(void *data, unsigned *pass)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (!cg || cg->shader->feedback_pass < 0)
      return false;

   *pass = cg->shader->feedback_pass;
   return true;
}

static bool gl_cg_mipmap_input(void *data, unsigned idx)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (cg && idx)
      return cg->shader->pass[idx - 1].mipmap;
   return false;
}

static struct video_shader *gl_cg_get_current_shader(void *data)
{
   cg_shader_data_t *cg = (cg_shader_data_t*)data;
   if (!cg)
      return NULL;
   return cg->shader;
}

const shader_backend_t gl_cg_backend = {
   gl_cg_init,
   gl_cg_deinit,
   gl_cg_set_params,
   gl_cg_set_uniform_parameter,
   gl_cg_compile_program,
   gl_cg_use,
   gl_cg_num,
   gl_cg_filter_type,
   gl_cg_wrap_type,
   gl_cg_shader_scale,
   gl_cg_set_coords,
   gl_cg_set_coords_fallback,
   gl_cg_set_mvp,
   gl_cg_get_prev_textures,
   gl_cg_get_feedback_pass,
   gl_cg_mipmap_input,
   gl_cg_get_current_shader,

   RARCH_SHADER_CG,
   "gl_cg"
};

