/* -*- mode: C; c-basic-offset: 2 -*- */
/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2020)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2020)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include <epoxy/gl.h>

#include "visu_extension.h"
#include "visu_configFile.h"
#include <coreTools/toolGl.h>

/**
 * SECTION:visu_extension
 * @short_description: All objects drawn by V_Sim are defined in by a
 * #VisuGlExt object
 *
 * <para>All objects that are drawn by V_Sim are handled by a
 * #VisuGlExt object. Such an object has an OpenGL list. This
 * list is only COMPILED. When V_Sim receives the 'OpenGLAskForReDraw'
 * or the 'OpenGLForceReDraw' signals, each list of all known
 * #VisuGlExt are excecuted. This excecution can be canceled if
 * the used flag of the #VisuGlExt object is set to FALSE. The
 * order in which the lists are called depends on the priority of the
 * #VisuGlExt object. This priority is set to
 * #VISU_GL_EXT_PRIORITY_NORMAL as default value, but it can be
 * tune by a call to visu_gl_ext_setPriority(). This priority is
 * an integer, the lower it is, the sooner the list is
 * excecuted.</para>
 */

#define FLAG_PARAMETER_MODE "extension_render"
#define DESC_PARAMETER_MODE "Rules the way OpenGl draws extensions (see opengl_render); name (string) value (string)"
#define FLAG_RESOURCE_MODE "glExtension_render"
#define DESC_RESOURCE_MODE "Rules the way OpenGl draws extensions (see gl_render); name (string) value (string)"
static guint _rMode;
static void exportRendering(GString *data, VisuData *dataObj);

static void _callList(const VisuGlExt *ext, const VisuGlExtGlobals *globals,
                      VisuGlRenderingMode *renderingMode, VisuGlRenderingMode globalRenderingMode);

enum
  {
    PROP_0,
    NAME_PROP,
    ACTIVE_PROP,
    LABEL_PROP,
    DESCRIPTION_PROP,
    PRIORITY_PROP,
    DIRTY_PROP,
    NGLPROG_PROP,
    NGLOBJ_PROP,
    NGLTEX_PROP,
    FOG_PROP,
    RMODE_PROP,
    N_PROP
  };
static GParamSpec *properties[N_PROP];

static GLenum _primitives[VISU_GL_N_PRIMITIVES];

struct _BufferMetaData
{
  guint dim;
  guint iProg;
  VisuGlExtPackingModels model;
  VisuGlExtPrimitives primitive;
  GArray *first;
  GArray *count;
  GArray *rgbaMat;
};

#define N_PROG_MAX 5
struct _Prog
{
  GLuint id;
  GLint uniforms[N_UNIFORMS];
};

static const GLfloat vTex[4][2][2] = {{{0.f, 0.f}, {0.f, 1.f}},
                                      {{1.f, 0.f}, {1.f, 1.f}},
                                      {{1.f, 1.f}, {1.f, 0.f}},
                                      {{0.f, 1.f}, {0.f, 0.f}}};

struct _VisuGlExtPrivate
{
  gboolean dispose_has_run;

  /* Some variable to describe this OpenGL extension.
     The attribute name is mandatory since it is
     used to identify the method. */
  gchar *name, *nameI18n;
  gchar *description;

  /* The id of the possible objects list brings by
     the extension is refered by this int. */
  guint nGlProg;
  struct _Prog glProgs[N_PROG_MAX];
  struct _Prog *curGlProg;
  guint nGlObj;
  GLuint *glBufs, *glVertexArrays;
  GArray *glBufMeta;
  struct _BufferMetaData *curGlBufMeta;
  guint nGlTex;
  GLuint texVertexArray;
  struct _Prog texProg;
  GLuint *glTexs;
  GLuint *glTexDims;
  GLuint glTexBuf;
  GArray *labels;

  /* Global translation to apply to the list before displaying. */
  float trans[3];

  /* A priority for the extension. */
  guint priority;

  gboolean withFog;

  /* Fine tune of rendering mode (VISU_GL_RENDERING_WIREFRAME, smooth...).
     When FOLLOW, the global value for rendering
     mode is used. Otherwise the value is stored in
     preferedRenderingMode. */
  VisuGlRenderingMode preferedRenderingMode;

  /* A boolean to know if this extension is actually used
     or not. */
  gboolean used;
  
  /* Set to VISU_GL_READY to skip draw calls. */
  VisuGlExtDirtyStates dirty;

  /* The context with gl options the extension is drawing to. */
  VisuGl *gl;
};

static VisuGlExtClass *my_class = NULL;

static void visu_gl_ext_dispose     (GObject* obj);
static void visu_gl_ext_finalize    (GObject* obj);
static void visu_gl_ext_get_property(GObject* obj, guint property_id,
                                     GValue *value, GParamSpec *pspec);
static void visu_gl_ext_set_property(GObject* obj, guint property_id,
                                     const GValue *value, GParamSpec *pspec);

static void onEntryMode(VisuGlExt *ext, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void _setTexBuffer(VisuGlExt *self);

G_DEFINE_TYPE_WITH_CODE(VisuGlExt, visu_gl_ext, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuGlExt))

static void visu_gl_ext_class_init(VisuGlExtClass *klass)
{
  VisuConfigFileEntry *confEntry, *oldEntry;

  g_debug("Visu Extension: creating the class of the object.");
  /* g_debug("                - adding new signals ;"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_gl_ext_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_gl_ext_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_gl_ext_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_gl_ext_get_property;

  /**
   * VisuGlExt::name:
   *
   * The name of the extension (used as an id).
   *
   * Since: 3.7
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), NAME_PROP,
     g_param_spec_string("name", "Name", "name (id) of extension", "",
                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
                         G_PARAM_STATIC_STRINGS));
  /**
   * VisuGlExt::active:
   *
   * The extension is used or not.
   *
   * Since: 3.7
   */
  properties[ACTIVE_PROP] = g_param_spec_boolean("active", "Active",
                                                 "extension is used or not", FALSE,
                                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property(G_OBJECT_CLASS(klass), ACTIVE_PROP,
                                  properties[ACTIVE_PROP]);
  /**
   * VisuGlExt::label:
   *
   * The label of extension (translated).
   *
   * Since: 3.7
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), LABEL_PROP,
     g_param_spec_string("label", "Label", "label (translated) of extension", "",
                         G_PARAM_CONSTRUCT | G_PARAM_READWRITE |
                         G_PARAM_STATIC_STRINGS));
  /**
   * VisuGlExt::description:
   *
   * The description of the extension.
   *
   * Since: 3.7
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), DESCRIPTION_PROP,
     g_param_spec_string("description", "Description", "description of extension", "",
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * VisuGlExt::priority:
   *
   * The drawing priority of the extension.
   *
   * Since: 3.7
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), PRIORITY_PROP,
     g_param_spec_uint("priority", "Priority", "drawing priority of extension",
                       VISU_GL_EXT_PRIORITY_BACKGROUND,
                       VISU_GL_EXT_PRIORITY_LAST,
                       VISU_GL_EXT_PRIORITY_NORMAL,
                       G_PARAM_CONSTRUCT | G_PARAM_READWRITE |
                       G_PARAM_STATIC_STRINGS));
  /**
   * VisuGlExt::nGlProg:
   *
   * The number of GL kernels the extension is dealing with.
   *
   * Since: 3.9
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), NGLPROG_PROP,
     g_param_spec_uint("nGlProg", "N GL kernels", "number of GL kernels dealt with",
                       0, N_PROG_MAX, 0,
                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
  /**
   * VisuGlExt::nGlObj:
   *
   * The number of GL list the extension is dealing with.
   *
   * Since: 3.7
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), NGLOBJ_PROP,
     g_param_spec_uint("nGlObj", "N GL objects", "number of GL lists dealt with",
                       0, 2048, 0,
                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
  /**
   * VisuGlExt::nGlTex:
   *
   * The number of GL textures the extension is dealing with.
   *
   * Since: 3.9
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), NGLTEX_PROP,
     g_param_spec_uint("nGlTex", "N GL textures", "number of GL textures dealt with",
                       0, 2048, 0,
                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
  /**
   * VisuGlExt::withFog:
   *
   * If the rendering element are sensitive to fog.
   *
   * Since: 3.9
   */
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), FOG_PROP,
     g_param_spec_boolean("withFog", "with fog", "rendering is impacted by fog",
                          FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
  /**
   * VisuGlExt::dirty:
   *
   * Rendering properties have changed and the object should be drawn again.
   *
   * Since: 3.8
   */
  properties[DIRTY_PROP] = g_param_spec_uint("dirty", "Dirty", "object rendering is out of date",
                                             VISU_GL_READY, VISU_GL_RENDER_REQUIRED, VISU_GL_DRAW_REQUIRED, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), DIRTY_PROP, properties[DIRTY_PROP]);
  /**
   * VisuGlExt::rendering-mode:
   *
   * The specific rendering mode of the extension or follow global setting.
   *
   * Since: 3.8
   */
  properties[RMODE_PROP] = g_param_spec_uint("rendering-mode", "Rendering mode",
                                             "specific rendering mode for the extension",
                                             VISU_GL_RENDERING_WIREFRAME,
                                             VISU_GL_RENDERING_FOLLOW,
                                             VISU_GL_RENDERING_FOLLOW,
                                             G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), RMODE_PROP, properties[RMODE_PROP]);
  
  oldEntry = visu_config_file_addEntry(VISU_CONFIG_FILE_PARAMETER,
                                       FLAG_PARAMETER_MODE, DESC_PARAMETER_MODE, 1, NULL);
  visu_config_file_entry_setVersion(oldEntry, 3.4f);
  confEntry = visu_config_file_addEnumEntry(VISU_CONFIG_FILE_RESOURCE,
                                            FLAG_RESOURCE_MODE,
                                            DESC_RESOURCE_MODE,
                                            &_rMode, visu_gl_rendering_getModeFromName, TRUE);
  visu_config_file_entry_setVersion(confEntry, 3.8f);
  visu_config_file_entry_setReplace(confEntry, oldEntry);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE, exportRendering);

  klass->allExtensions = (GList*)0;

  my_class = klass;

  _primitives[VISU_GL_POINTS] = GL_POINTS;
  _primitives[VISU_GL_LINE_LOOP] = GL_LINE_LOOP;
  _primitives[VISU_GL_LINE_STRIP] = GL_LINE_STRIP;
  _primitives[VISU_GL_LINES] = GL_LINES;
  _primitives[VISU_GL_TRIANGLE_FAN] = GL_TRIANGLE_FAN;
  _primitives[VISU_GL_TRIANGLE_STRIP] = GL_TRIANGLE_STRIP;
  _primitives[VISU_GL_TRIANGLES] = GL_TRIANGLES;
}

static void _freeBufferMetaData(struct _BufferMetaData *meta)
{
  if (meta->first)
    g_array_free(meta->first, TRUE);
  if (meta->count)
    g_array_free(meta->count, TRUE);
  if (meta->rgbaMat)
    g_array_free(meta->rgbaMat, TRUE);
}

static void visu_gl_ext_init(VisuGlExt *ext)
{
  g_debug("Visu Extension: initializing a new object (%p).",
	      (gpointer)ext);
  ext->priv = visu_gl_ext_get_instance_private(ext);
  ext->priv->dispose_has_run = FALSE;

  /* Set-up all not parameters attributes. */
  ext->priv->used = TRUE;
  ext->priv->dirty = VISU_GL_REBUILD_REQUIRED;
  ext->priv->priority = VISU_GL_EXT_PRIORITY_NORMAL;
  ext->priv->nGlProg = 0;
  ext->priv->nGlObj = 0;
  ext->priv->curGlProg = (struct _Prog*)0;
  ext->priv->glBufs = (GLuint*)0;
  ext->priv->glVertexArrays = (GLuint*)0;
  ext->priv->glBufMeta = g_array_new(FALSE, TRUE, sizeof(struct _BufferMetaData));
  ext->priv->curGlBufMeta = (struct _BufferMetaData*)0;
  g_array_set_clear_func(ext->priv->glBufMeta, (GDestroyNotify)_freeBufferMetaData);
  ext->priv->texProg.id = 0;
  ext->priv->texVertexArray = 0;
  ext->priv->nGlTex = 0;
  ext->priv->glTexs = (GLuint*)0;
  ext->priv->glTexDims = (GLuint*)0;
  ext->priv->glTexBuf = 0;
  ext->priv->labels = (GArray*)0;
  ext->priv->withFog = FALSE;
  ext->priv->preferedRenderingMode = VISU_GL_RENDERING_FOLLOW;

  ext->priv->trans[0] = 0.f;
  ext->priv->trans[1] = 0.f;
  ext->priv->trans[2] = 0.f;

  ext->priv->gl = (VisuGl*)0;

  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCE_MODE,
                          G_CALLBACK(onEntryMode), (gpointer)ext, G_CONNECT_SWAPPED);

  my_class->allExtensions = g_list_append(my_class->allExtensions, (gpointer)ext);
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_gl_ext_dispose(GObject* obj)
{
  VisuGlExt *ext;

  g_debug("Visu Extension: dispose object %p.", (gpointer)obj);

  ext = VISU_GL_EXT(obj);
  if (ext->priv->dispose_has_run)
    return;
  ext->priv->dispose_has_run = TRUE;

  if (ext->priv->labels)
    g_array_unref(ext->priv->labels);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_gl_ext_finalize(GObject* obj)
{
  VisuGlExtPrivate *ext;

  g_return_if_fail(obj);

  g_debug("Visu Extension: finalize object %p.", (gpointer)obj);

  ext = VISU_GL_EXT(obj)->priv;
  visu_gl_ext_release(VISU_GL_EXT(obj));
  if (ext->glBufs)
    g_free(ext->glBufs);
  if (ext->glVertexArrays)
    g_free(ext->glVertexArrays);
  g_array_free(ext->glBufMeta, TRUE);
  if (ext->glTexs)
    g_free(ext->glTexs);
  if (ext->glTexDims)
    g_free(ext->glTexDims);
  if (ext->name)
    g_free(ext->name);
  if (ext->nameI18n)
    g_free(ext->nameI18n);
  if (ext->description)
    g_free(ext->description);

  my_class->allExtensions = g_list_remove_all(my_class->allExtensions, (gpointer)obj);

  /* Chain up to the parent class */
  g_debug("Visu Extension: chain to parent.");
  G_OBJECT_CLASS(visu_gl_ext_parent_class)->finalize(obj);
  g_debug("Visu Extension: freeing ... OK.");
}
static void visu_gl_ext_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec)
{
  VisuGlExtPrivate *self = VISU_GL_EXT(obj)->priv;

  g_debug("Visu Extension: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case NAME_PROP:
      g_value_set_string(value, self->name);
      g_debug("%s.", self->name);
      break;
    case LABEL_PROP:
      if (self->nameI18n && self->nameI18n[0])
        g_value_set_string(value, self->nameI18n);
      else
        g_value_set_string(value, self->name);
      g_debug("%s.", self->nameI18n);
      break;
    case DESCRIPTION_PROP:
      g_value_set_string(value, self->description);
      g_debug("%s.", self->description);
      break;
    case ACTIVE_PROP:
      g_value_set_boolean(value, self->used);
      g_debug("%d.", self->used);
      break;
    case PRIORITY_PROP:
      g_value_set_uint(value, self->priority);
      g_debug("%d.", self->priority);
      break;
    case NGLPROG_PROP:
      g_value_set_uint(value, self->nGlProg);
      g_debug("%d.", self->nGlProg);
      break;
    case NGLOBJ_PROP:
      g_value_set_uint(value, self->nGlObj);
      g_debug("%d.", self->nGlObj);
      break;
    case NGLTEX_PROP:
      g_value_set_uint(value, self->nGlTex);
      g_debug("%d.", self->nGlTex);
      break;
    case DIRTY_PROP:
      g_value_set_uint(value, self->dirty);
      g_debug("%d.", self->dirty);
      break;
    case FOG_PROP:
      g_value_set_boolean(value, self->withFog);
      g_debug("%d.", self->withFog);
      break;
    case RMODE_PROP:
      g_value_set_uint(value, self->preferedRenderingMode);
      g_debug("%d.", self->preferedRenderingMode);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_gl_ext_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec)
{
  VisuGlExtPrivate *self = VISU_GL_EXT(obj)->priv;

  g_debug("Visu Extension: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case NAME_PROP:
      self->name = g_value_dup_string(value);
      g_debug("%s.", self->name);
      break;
    case LABEL_PROP:
      self->nameI18n = g_value_dup_string(value);
      g_debug("%s.", self->nameI18n);
      break;
    case DESCRIPTION_PROP:
      self->description = g_value_dup_string(value);
      g_debug("%s.", self->description);
      break;
    case ACTIVE_PROP:
      visu_gl_ext_setActive(VISU_GL_EXT(obj), g_value_get_boolean(value));
      g_debug("%d.", self->used);
      /* visu_gl_ext_draw(VISU_GL_EXT(obj)); */
      break;
    case PRIORITY_PROP:
      self->priority = g_value_get_uint(value);
      g_debug("%d.", self->priority);
      break;
    case NGLPROG_PROP:
      self->nGlProg = g_value_get_uint(value);
      g_debug("%d.", self->nGlProg);
      break;
    case NGLOBJ_PROP:
      self->nGlObj = g_value_get_uint(value);
      g_debug("%d.", self->nGlObj);
      break;
    case NGLTEX_PROP:
      self->nGlTex = g_value_get_uint(value);
      g_debug("%d.", self->nGlTex);
      break;
    case DIRTY_PROP:
      g_debug("%d.", g_value_get_uint(value));
      visu_gl_ext_setDirty(VISU_GL_EXT(obj), g_value_get_uint(value));
      break;
    case FOG_PROP:
      self->withFog = g_value_get_boolean(value);
      g_debug("%d.", self->withFog);
      break;
    case RMODE_PROP:
      g_debug("%d.", g_value_get_uint(value));
      visu_gl_ext_setPreferedRenderingMode(VISU_GL_EXT(obj), g_value_get_uint(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_gl_ext_setDirty:
 * @ext: a #VisuGlExt object.
 * @status: a #VisuGlExtDirtyStates value.
 *
 * Set an internal flag to mark that @ext should be redrawn before
 * next OpenGL frame view.
 *
 * Since: 3.8
 *
 * Returns: TRUE is status is actually changed.
 **/
gboolean visu_gl_ext_setDirty(VisuGlExt *ext, VisuGlExtDirtyStates status)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(ext), FALSE);

  /* It's an exception here, it's VisuGlExtSet that will ignore
     redundant call to dirty status. */
  if (ext->priv->dirty == status)
    return FALSE;

  g_debug("Visu Extension: '%s' becomes dirty (%d).", ext->priv->name, status);
  if (ext->priv->dirty != VISU_GL_REBUILD_REQUIRED)
    ext->priv->dirty = status;
  if (status != VISU_GL_READY)
    g_object_notify_by_pspec(G_OBJECT(ext), properties[DIRTY_PROP]);
  return TRUE;
}

/**
 * visu_gl_ext_getActive:
 * @extension: the extension.
 *
 * Get if the extension is used or not.
 *
 * Returns: TRUE if used, FALSE otherwise.
 */
gboolean visu_gl_ext_getActive(VisuGlExt* extension)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), FALSE);

  if (extension)
    return extension->priv->used;
  else
    return FALSE;
}
/**
 * visu_gl_ext_setActive:
 * @extension: the extension,
 * @value: the new value.
 *
 * Set if an extension is actually used or not.
 */
gboolean visu_gl_ext_setActive(VisuGlExt* extension, gboolean value)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), FALSE);

  if (extension->priv->used == value)
    return FALSE;

  g_debug("Visu Extension: '%s' becomes active (%d).",
              extension->priv->name, value);
  extension->priv->used = value;
  g_object_notify_by_pspec(G_OBJECT(extension), properties[ACTIVE_PROP]);

  return TRUE;
}
/**
 * visu_gl_ext_getPriority:
 * @extension: a #VisuGlExt object.
 *
 * Inquire the priority of @extension.
 *
 * Since: 3.8
 *
 * Returns: the #VisuGlExt priority.
 **/
guint visu_gl_ext_getPriority(VisuGlExt *extension)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), VISU_GL_EXT_PRIORITY_BACKGROUND);

  return extension->priv->priority;
}

/**
 * visu_gl_ext_setPreferedRenderingMode:
 * @extension: a #VisuGlExt object ;
 * @value: see #VisuGlRenderingMode to choose one.
 *
 * This method is used to specify the rendering mode that the extension should use
 * to be drawn. If the @value is set to #VISU_GL_RENDERING_FOLLOW, the
 * extension follows the global setting for rendering mode.
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_ext_setPreferedRenderingMode(VisuGlExt* extension,
                                              VisuGlRenderingMode value)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), FALSE);
  g_return_val_if_fail(value < VISU_GL_RENDERING_N_MODES ||
		       value == VISU_GL_RENDERING_FOLLOW, FALSE);

  if (extension->priv->preferedRenderingMode == value)
    return FALSE;

  extension->priv->preferedRenderingMode = value;
  g_object_notify_by_pspec(G_OBJECT(extension), properties[RMODE_PROP]);

  return TRUE;
}
/**
 * visu_gl_ext_getPreferedRenderingMode:
 * @extension: a #VisuGlExt method.
 *
 * Each #VisuGlExt method can draw in a mode different from the
 * global one, see #VisuGlRenderingMode. See also
 * visu_gl_ext_setPreferedRenderingMode().
 *
 * Since: 3.7
 *
 * Returns: the prefered rendering mode of this @extension.
 **/
VisuGlRenderingMode visu_gl_ext_getPreferedRenderingMode(VisuGlExt* extension)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), VISU_GL_RENDERING_FOLLOW);
  return extension->priv->preferedRenderingMode;
}
/**
 * visu_gl_ext_getGlBuffer:
 * @ext: a #VisuGlExt object
 * @id: an id (within [0; nGlObj[)
 *
 * Retrieve the OpenGL index used to identify the @id buffer.
 *
 * Since: 3.9
 *
 * Returns: a GL buffer index.
 **/
guint visu_gl_ext_getGlBuffer(const VisuGlExt *ext, guint id)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(ext) && id < ext->priv->nGlObj, 0);
  return ext->priv->glBufs[id];
}
/**
 * visu_gl_ext_getBufferDimension:
 * @ext: a #VisuGlExt object
 *
 * Retrieve the size of the current buffer set with visu_gl_ext_setBuffer().
 *
 * Since: 3.9
 *
 * Returns: a GL buffer index.
 **/
guint visu_gl_ext_getBufferDimension(const VisuGlExt *ext)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(ext) && ext->priv->curGlBufMeta, 0);
  return ext->priv->curGlBufMeta->dim;
}
/**
 * visu_gl_ext_setTranslation:
 * @extension: a #VisuGlExt method.
 * @trans: (array fixed-size=3): a translation vector in real space.
 *
 * Change the translation the extension is drawn at.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the translations are indeed changed.
 **/
gboolean visu_gl_ext_setTranslation(VisuGlExt *extension, const gfloat trans[3])
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), FALSE);

  if (extension->priv->trans[0] == trans[0] &&
      extension->priv->trans[1] == trans[1] &&
      extension->priv->trans[2] == trans[2])
    return FALSE;

  g_debug("Visu Extension: set translation to %gx%gx%g.", trans[0], trans[1], trans[2]);
  extension->priv->trans[0] = trans[0];
  extension->priv->trans[1] = trans[1];
  extension->priv->trans[2] = trans[2];

  return TRUE;
}
/**
 * visu_gl_ext_getTranslation:
 * @extension: a #VisuGlExt method.
 * @trans: (out caller-allocates) (array fixed-size=3): a location to store the translation.
 *
 * Inquire the translation the extension is drawn with.
 *
 * Since: 3.9
 **/
void visu_gl_ext_getTranslation(const VisuGlExt *extension, gfloat trans[3])
{
  g_return_if_fail(VISU_IS_GL_EXT(extension));

  trans[0] = extension->priv->trans[0];
  trans[1] = extension->priv->trans[1];
  trans[2] = extension->priv->trans[2];
}

/********************/
/* Class functions. */
/********************/

/**
 * visu_gl_ext_draw:
 * @ext: a #VisuGlExt object.
 *
 * Send object data of @ext to GPU, if necessary.
 *
 * Since: 3.9
 */
void visu_gl_ext_draw(VisuGlExt *ext)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext));

  if (!ext->priv->used)
    return;

  if (ext->priv->dirty == VISU_GL_REBUILD_REQUIRED)
    visu_gl_ext_rebuild(ext);
  if (ext->priv->dirty == VISU_GL_DRAW_REQUIRED)
    {
      g_debug("Visu Extension: draw %s (%d).", ext->priv->name, ext->priv->dirty);
      if (VISU_GL_EXT_GET_CLASS(ext)->draw)
        VISU_GL_EXT_GET_CLASS(ext)->draw(ext);
      ext->priv->dirty = VISU_GL_RENDER_REQUIRED;
    }
}

/**
 * visu_gl_ext_call:
 * @extension: a #VisuGlExt object.
 * @globals: global uniform values.
 *
 * Select the #VisuGlExt matching the given @name and call
 * it. The call is indeed done only if the extension is used. If
 * @lastOnly is TRUE, the list is called only if it has a
 * #VISU_GL_EXT_PRIORITY_LAST priority. On the contrary the list
 * is called only if its priority is lower than
 * #VISU_GL_EXT_PRIORITY_LAST.
 */
void visu_gl_ext_call(const VisuGlExt *ext, const VisuGlExtGlobals *globals)
{
  VisuGlRenderingMode renderingMode, globalRenderingMode;
  VisuGlExtGlobals translated;

  g_return_if_fail(VISU_IS_GL_EXT(ext) && globals);
  /* g_debug("Visu Extension: call '%s' list.", ext->priv->name); */
  /* g_debug("|  has %d ref counts.", G_OBJECT(ext)->ref_count); */

  if (!ext->priv->used)
    return;

  globalRenderingMode = visu_gl_getMode(ext->priv->gl);
  renderingMode = globalRenderingMode;
  translated = *globals;
  tool_gl_matrix_translate(&translated.mvp, ext->priv->trans);
  tool_gl_matrix_translate(&translated.MV, ext->priv->trans);
  _callList(ext, &translated, &renderingMode, globalRenderingMode);
  if (renderingMode != globalRenderingMode)
    /* Return the rendering mode to normal. */
    visu_gl_rendering_applyMode(globalRenderingMode);
  ext->priv->dirty = VISU_GL_READY;
}
/**
 * visu_gl_ext_callText:
 * @ext: a #VisuGlExt object.
 * @globals: global uniform values.
 *
 * Specifically call the text render routine for @ext.
 *
 * Since: 3.9
 */
void visu_gl_ext_callText(const VisuGlExt *ext, const VisuGlExtGlobals *globals)
{
  ToolGlMatrix MVP;

  g_return_if_fail(VISU_IS_GL_EXT(ext) && globals);

  if (!ext->priv->used || !VISU_GL_EXT_GET_CLASS(ext)->renderText)
    return;
  if (!ext->priv->texProg.id)
    return;

  g_debug("Visu Extension: rendering texts for '%s'.", ext->priv->name);
  MVP = globals->mvp;
  tool_gl_matrix_translate(&MVP, ext->priv->trans);
  glUseProgram(ext->priv->texProg.id);
  glBindVertexArray(ext->priv->texVertexArray);
  if (ext->priv->texProg.uniforms[UNIFORM_MVP] >= 0)
    glUniformMatrix4fv(ext->priv->texProg.uniforms[UNIFORM_MVP], 1, GL_FALSE, MVP.c[0]);
  if (ext->priv->texProg.uniforms[UNIFORM_MV] >= 0)
    glUniformMatrix4fv(ext->priv->texProg.uniforms[UNIFORM_MV], 1, GL_FALSE, globals->MV.c[0]);
  if (ext->priv->texProg.uniforms[UNIFORM_RESOLUTION] >= 0)
    glUniform2fv(ext->priv->texProg.uniforms[UNIFORM_RESOLUTION], 1, globals->resolution);
  if (ext->priv->texProg.uniforms[UNIFORM_FOG] >= 0)
    glUniform2fv(ext->priv->texProg.uniforms[UNIFORM_FOG], 1, globals->fogStartEnd);
  if (ext->priv->texProg.uniforms[UNIFORM_FOG_COLOR] >= 0)
    glUniform4fv(ext->priv->texProg.uniforms[UNIFORM_FOG_COLOR], 1, globals->fogColor);
  ext->priv->curGlProg = &ext->priv->texProg;
  VISU_GL_EXT_GET_CLASS(ext)->renderText(ext);
  ext->priv->curGlProg = (struct _Prog*)0;
  glBindVertexArray(0);
  glUseProgram(0);
}
/**
 * visu_gl_ext_rebuild:
 * @self: a #VisuGlExt object.
 *
 * This routine does not sort the extension on their priority and
 * should be used only to draw some selected extensions. This method
 * is called automatically for all extensions in a #VisuGlExtSet when required.
 */
void visu_gl_ext_rebuild(VisuGlExt *self)
{
  g_return_if_fail(VISU_IS_GL_EXT(self));

  g_debug("Visu Extension: rebuilding '%s' list (%d, %d).", self->priv->name, self->priv->nGlObj, self->priv->nGlTex);

  if (self->priv->nGlObj)
    {
      if (!self->priv->glBufs)
        self->priv->glBufs = g_malloc0(sizeof(GLuint) * self->priv->nGlObj);
      glGenBuffers(self->priv->nGlObj, self->priv->glBufs);
      if (!self->priv->glVertexArrays)
        self->priv->glVertexArrays = g_malloc0(sizeof(GLuint) * self->priv->nGlObj);
      glGenVertexArrays(self->priv->nGlObj, self->priv->glVertexArrays);
      g_array_set_size(self->priv->glBufMeta, self->priv->nGlObj);
    }
  if (self->priv->nGlTex)
    {
      if (!self->priv->glTexs)
        self->priv->glTexs = g_malloc0(sizeof(GLuint) * self->priv->nGlTex);
      if (!self->priv->glTexDims)
        self->priv->glTexDims = g_malloc(sizeof(GLuint) * self->priv->nGlTex * 2);
      glGenTextures(self->priv->nGlTex, self->priv->glTexs);
    }
  if (VISU_GL_EXT_GET_CLASS(self)->rebuild)
    VISU_GL_EXT_GET_CLASS(self)->rebuild(self);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
  if (VISU_GL_EXT_GET_CLASS(self)->renderText)
    _setTexBuffer(self);
  self->priv->dirty = VISU_GL_DRAW_REQUIRED;
}
/**
 * visu_gl_ext_release:
 * @self: a #VisuGlExt object.
 *
 * This routine frees the OpenGL resources when not needed anymore for
 * the current context.
 */
void visu_gl_ext_release(VisuGlExt *self)
{
  g_return_if_fail(VISU_IS_GL_EXT(self));

  g_debug("Visu Extension: releasing '%s' list.", self->priv->name);

  if (VISU_GL_EXT_GET_CLASS(self)->release)
    VISU_GL_EXT_GET_CLASS(self)->release(self);
  if (self->priv->nGlObj && self->priv->glBufs && self->priv->glBufs[0])
    {
      glDeleteBuffers(self->priv->nGlObj, self->priv->glBufs);
      self->priv->glBufs[0] = 0;
    }
  if (self->priv->texProg.id)
    {
      glDeleteProgram(self->priv->texProg.id);
      self->priv->texProg.id = 0;
    }
  if (self->priv->nGlTex && self->priv->glTexs && self->priv->glTexs[0])
    {
      glDeleteTextures(self->priv->nGlTex, self->priv->glTexs);
      self->priv->glTexs[0] = 0;
    }
  if (self->priv->glTexBuf)
    {
      glDeleteBuffers(1, &self->priv->glTexBuf);
      self->priv->glTexBuf = 0;
    }
  if (self->priv->nGlObj && self->priv->glVertexArrays && self->priv->glVertexArrays[0])
    {
      glDeleteVertexArrays(self->priv->nGlObj, self->priv->glVertexArrays);
      self->priv->glVertexArrays[0] = 0;
    }
  if (self->priv->texVertexArray)
    {
      glDeleteVertexArrays(1, &self->priv->texVertexArray);
      self->priv->texVertexArray = 0;
    }
}
/**
 * visu_gl_ext_setGlView:
 * @ext: a #VisuGlExt object.
 * @view: a #VisuGlView object.
 *
 * Attach a @view to the @self extension.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the @view is changed.
 */
gboolean visu_gl_ext_setGlView(VisuGlExt *ext, VisuGlView *view)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(ext), FALSE);

  g_debug("Visu Extension: set view %p to '%s' (%d).",
              (gpointer)view, ext->priv->name, ext->priv->used);

  return (VISU_GL_EXT_GET_CLASS(ext)->setGlView &&
          VISU_GL_EXT_GET_CLASS(ext)->setGlView(ext, view));
}

/**
 * visu_gl_ext_getGlContext:
 * @extension: a #VisuGlExt object.
 *
 * The #VisuGl object this extension draws to.
 *
 * Since: 3.8
 *
 * Returns: (transfer none) (allow-none): the #VisuGl object this
 * extension draws to.
 **/
VisuGl* visu_gl_ext_getGlContext(VisuGlExt* extension)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), (VisuGl*)0);
  return extension->priv->gl;
}
/**
 * visu_gl_ext_setGlContext:
 * @extension: a #VisuGlExt object.
 * @gl: (transfer none): a #VisuGl object.
 *
 * Associate @gl to @extension.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the value is actually changed.
 **/
gboolean visu_gl_ext_setGlContext(VisuGlExt* extension, VisuGl *gl)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), FALSE);
  if (extension->priv->gl == gl)
    return FALSE;
  extension->priv->gl = gl;
  return TRUE;
}

/**
 * visu_gl_ext_getName:
 * @extension: a #VisuGlExt object.
 *
 * Retrieve the name of the extension.
 *
 * Since: 3.8
 *
 * Returns: the name of the extension.
 **/
const gchar* visu_gl_ext_getName(const VisuGlExt *extension)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(extension), (const gchar*)0);
  
  return extension->priv->name;
}

/**
 * visu_gl_ext_setShader:
 * @ext: a #VisuGlExt object.
 * @id: a shader id.
 * @vertexSource: vertex source.
 * @fragmentSource: fragment source.
 * @uniforms: labels of user defined uniforms.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create the shader defined by @vertexSource and @fragmentSource and affect it to @id.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean visu_gl_ext_setShader(VisuGlExt *ext, guint id,
                               const char *vertexSource, const char *fragmentSource,
                               const char **uniforms, GError **error)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(ext) && id < ext->priv->nGlProg, FALSE);

  if (ext->priv->glProgs[id].id)
    {
      glDeleteProgram(ext->priv->glProgs[id].id);
      ext->priv->glProgs[id].id = 0;
    }

  if (!tool_gl_createShader(&ext->priv->glProgs[id].id, vertexSource, fragmentSource, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    ext->priv->glProgs[id].uniforms[i] = -1;
  ext->priv->glProgs[id].uniforms[UNIFORM_MVP] = glGetUniformLocation(ext->priv->glProgs[id].id, "mvp");
  ext->priv->glProgs[id].uniforms[UNIFORM_RESOLUTION] = glGetUniformLocation(ext->priv->glProgs[id].id, "resolution");
  ext->priv->glProgs[id].uniforms[UNIFORM_FOG] = glGetUniformLocation(ext->priv->glProgs[id].id, "fog");
  ext->priv->glProgs[id].uniforms[UNIFORM_FOG_COLOR] = glGetUniformLocation(ext->priv->glProgs[id].id, "fogColor");
  ext->priv->glProgs[id].uniforms[UNIFORM_COLOR] = glGetUniformLocation(ext->priv->glProgs[id].id, "color");
  if (uniforms)
    for (guint i = UNIFORM_USER; uniforms[i - UNIFORM_USER] && i < N_UNIFORMS; i++)
      ext->priv->glProgs[id].uniforms[i] = glGetUniformLocation(ext->priv->glProgs[id].id, uniforms[i - UNIFORM_USER]);

  return TRUE;
}

/**
 * visu_gl_ext_setShaderById:
 * @ext: a #VisuGlExt object.
 * @id: a shader id.
 * @shader: a predefined shader id.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create the shader defined by @shader and affect it to @id.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean visu_gl_ext_setShaderById(VisuGlExt *ext, guint id, VisuGlExtShaderIds shader, GError **error)
{
  g_return_val_if_fail(VISU_IS_GL_EXT(ext) && id < ext->priv->nGlProg, FALSE);

  if (ext->priv->glProgs[id].id)
    {
      glDeleteProgram(ext->priv->glProgs[id].id);
      ext->priv->glProgs[id].id = 0;
    }

  switch (shader)
    {
    case VISU_GL_SHADER_ORTHO:
      return tool_gl_createOrthoShader(&ext->priv->glProgs[id].id,
                                       ext->priv->glProgs[id].uniforms, error);
    case VISU_GL_SHADER_FLAT:
      return tool_gl_createFlatShader(&ext->priv->glProgs[id].id,
                                      ext->priv->glProgs[id].uniforms,
                                      ext->priv->withFog, error);
    case VISU_GL_SHADER_SIMPLE:
      return tool_gl_createSimpleShader(&ext->priv->glProgs[id].id,
                                        ext->priv->glProgs[id].uniforms,
                                        ext->priv->withFog, error);
    case VISU_GL_SHADER_LINE:
      return tool_gl_createLineShader(&ext->priv->glProgs[id].id,
                                      ext->priv->glProgs[id].uniforms,
                                      ext->priv->withFog, error);
    case VISU_GL_SHADER_COLORED_LINE:
      return tool_gl_createColoredLineShader(&ext->priv->glProgs[id].id,
                                             ext->priv->glProgs[id].uniforms,
                                             ext->priv->withFog, error);
    case VISU_GL_SHADER_MATERIAL:
      return tool_gl_createMaterialShader(&ext->priv->glProgs[id].id,
                                          ext->priv->glProgs[id].uniforms,
                                          ext->priv->withFog, error);
    }

  g_set_error(error, TOOL_GL_ERROR, ERROR_SHADER_ID, "Unknown shader id: %d", shader);
  return FALSE;
}

/**
 * visu_gl_ext_setViewport:
 * @ext: a #VisuGlExt object.
 * @x: lower-left corner.
 * @y: lower-left corner.
 * @width: the new width.
 * @height: the new height.
 *
 * Change the current viewport.
 *
 * Since: 3.9
 **/
void visu_gl_ext_setViewport(const VisuGlExt *ext, guint x, guint y, guint width, guint height)
{
  GLfloat res[2] = {width, height};

  g_return_if_fail(VISU_IS_GL_EXT(ext));

  g_debug("Visu Extension: set viewport at %dx%d size %dx%d.", x, y, width, height);
  glViewport(x, y, width, height);
  if (ext->priv->curGlProg)
    {
      if (ext->priv->curGlProg->uniforms[UNIFORM_RESOLUTION] >= 0)
        glUniform2fv(ext->priv->curGlProg->uniforms[UNIFORM_RESOLUTION], 1, res);
    }
  else
    for (guint i = 0; i < ext->priv->nGlProg; i++)
      {
        glUseProgram(ext->priv->glProgs[i].id);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_RESOLUTION] >= 0)
          glUniform2fv(ext->priv->glProgs[i].uniforms[UNIFORM_RESOLUTION], 1, res);
        glUseProgram(0);
      }
}
/**
 * visu_gl_ext_setUniformMVP:
 * @ext: a #VisuGlExt object.
 * @iProg: a shader id.
 * @MVP: a #ToolGlMatrix object.
 *
 * If @iProg is using UNIFORM_MVP, then @MVP is send to the GPU.
 * It is using the current shader if it corresponds to @iProg, otherwise
 * the current program is bound to @iProg and then unbound.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformMVP(const VisuGlExt *ext, guint iProg,
                               const ToolGlMatrix *MVP)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && iProg < ext->priv->nGlProg);
  
  if (ext->priv->glProgs[iProg].uniforms[UNIFORM_MVP] >= 0 && MVP)
    {
      if (!ext->priv->curGlProg || ext->priv->curGlProg->id != ext->priv->glProgs[iProg].id)
        glUseProgram(ext->priv->glProgs[iProg].id);
      glUniformMatrix4fv(ext->priv->glProgs[iProg].uniforms[UNIFORM_MVP], 1, GL_FALSE, MVP->c[0]);
      if (!ext->priv->curGlProg || ext->priv->curGlProg->id != ext->priv->glProgs[iProg].id)
        glUseProgram(0);
    }
}
/**
 * visu_gl_ext_setUniformMV:
 * @ext: a #VisuGlExt object.
 * @iProg: a shader id.
 * @MV: a #ToolGlMatrix object.
 *
 * If @iProg is using UNIFORM_MV, then @MV is send to the GPU.
 * It is using the current shader if it corresponds to @iProg, otherwise
 * the current program is bound to @iProg and then unbound.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformMV(const VisuGlExt *ext, guint iProg,
                              const ToolGlMatrix *MV)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && iProg < ext->priv->nGlProg);
  
  if (ext->priv->glProgs[iProg].uniforms[UNIFORM_MV] >= 0 && MV)
    {
      if (!ext->priv->curGlProg || ext->priv->curGlProg->id != ext->priv->glProgs[iProg].id)
        glUseProgram(ext->priv->glProgs[iProg].id);
      glUniformMatrix4fv(ext->priv->glProgs[iProg].uniforms[UNIFORM_MV], 1, GL_FALSE, MV->c[0]);
      if (!ext->priv->curGlProg || ext->priv->curGlProg->id != ext->priv->glProgs[iProg].id)
        glUseProgram(0);
    }
}
/**
 * visu_gl_ext_setUniformCamera:
 * @ext: a #VisuGlExt object.
 * @iProg: a shader id.
 * @xyz: (array fixed-size=3): the camera position in object space.
 *
 * If @iProg is using UNIFORM_CAMERA_POS, then @xyz is send to the GPU.
 * It is using the current shader if it corresponds to @iProg, otherwise
 * the current program is bound to @iProg and then unbound.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformCamera(const VisuGlExt *ext, guint iProg,
                                  const gfloat xyz[3])
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && iProg < ext->priv->nGlProg);
  
  if (ext->priv->glProgs[iProg].uniforms[UNIFORM_CAMERA_POS] >= 0)
    {
      if (!ext->priv->curGlProg || ext->priv->curGlProg->id != ext->priv->glProgs[iProg].id)
        glUseProgram(ext->priv->glProgs[iProg].id);
      glUniform3fv(ext->priv->glProgs[iProg].uniforms[UNIFORM_CAMERA_POS], 1, xyz);
      if (!ext->priv->curGlProg || ext->priv->curGlProg->id != ext->priv->glProgs[iProg].id)
        glUseProgram(0);
    }
}
/**
 * visu_gl_ext_setUniformRGBA:
 * @ext: a #VisuGlExt object.
 * @rgba: (array fixed-size=4): a color.
 *
 * Send @rgba to the GPU using the uniform defined by UNIFORM_COLOR.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformRGBA(const VisuGlExt *ext, const gfloat rgba[4])
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && ext->priv->curGlProg);
  
  glUniform4fv(ext->priv->curGlProg->uniforms[UNIFORM_COLOR], 1, rgba);
}
/**
 * visu_gl_ext_setUniformMaterial:
 * @ext: a #VisuGlExt object.
 * @material: (array fixed-size=5): a material.
 *
 * Send @material to the GPU using the various uniforms used for light.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformMaterial(const VisuGlExt *ext, const gfloat material[TOOL_MATERIAL_N_VALUES])
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && ext->priv->curGlProg);

  if (ext->priv->curGlProg->uniforms[UNIFORM_AMBIENT] >= 0)
    {
      glUniform1f(ext->priv->curGlProg->uniforms[UNIFORM_AMBIENT], material[TOOL_MATERIAL_AMB] * 1.2f);
      glUniform1f(ext->priv->curGlProg->uniforms[UNIFORM_DIFFUSE], material[TOOL_MATERIAL_DIF]);
      glUniform1f(ext->priv->curGlProg->uniforms[UNIFORM_SPECULAR], material[TOOL_MATERIAL_SPE]);
      glUniform1f(ext->priv->curGlProg->uniforms[UNIFORM_SHININESS], material[TOOL_MATERIAL_SHI] * 32.f);
      glUniform1f(ext->priv->curGlProg->uniforms[UNIFORM_EMISSION], material[TOOL_MATERIAL_EMI]);
    }
}
/**
 * visu_gl_ext_setUniformStipple:
 * @ext: a #VisuGlExt object.
 * @stipple: a stipple pattern.
 *
 * Send @stipple to the GPU using the uniform UNIFORM_STIPPLE.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformStipple(const VisuGlExt *ext, const guint16 stipple)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && ext->priv->curGlProg);
  
  glUniform1ui(ext->priv->curGlProg->uniforms[UNIFORM_STIPPLE], stipple);
}
/**
 * visu_gl_ext_setUniformXYZ:
 * @ext: a #VisuGlExt object.
 * @id: a uniform id.
 * @xyz: (array fixed-size=3): a position.
 *
 * Send @xyz to the GPU using the uniform defined by @id.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformXYZ(const VisuGlExt *ext, guint id, const gfloat xyz[3])
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && ext->priv->curGlProg);
  g_return_if_fail(id < N_UNIFORMS);
  
  glUniform3fv(ext->priv->curGlProg->uniforms[id], 1, xyz);
}

/**
 * visu_gl_ext_addBuffers:
 * @self: a #VisuGlExt object.
 * @number: a number.
 *
 * Create @number additional buffers. This method
 * should be called only for the current OpenGL context. This should
 * be used only for #VisuGlExt with varying number of
 * array buffers. Otherwise, the number of array buffers should be declared as a
 * class parameter.
 *
 * Since: 3.9
 *
 * Returns: the index to be used in visu_gl_ext_getGlBuffer(), or
 * %G_MAXUINT on error.
 **/
guint visu_gl_ext_addBuffers(VisuGlExt *self, guint number)
{
  guint nOld;

  g_return_val_if_fail(VISU_IS_GL_EXT(self), G_MAXUINT);

  if (!number)
    return G_MAXUINT;

  nOld = self->priv->nGlObj;
  self->priv->nGlObj += number;
  self->priv->glBufs = g_realloc(self->priv->glBufs,
                                 sizeof(GLuint) * self->priv->nGlObj);
  glGenBuffers(number, self->priv->glBufs + nOld);
  self->priv->glVertexArrays = g_realloc(self->priv->glVertexArrays,
                                        sizeof(GLuint) * self->priv->nGlObj);
  glGenVertexArrays(number, self->priv->glVertexArrays + nOld);
  g_array_set_size(self->priv->glBufMeta, self->priv->nGlObj);

  return nOld;
}
/**
 * visu_gl_ext_bookBuffers:
 * @self: a #VisuGlExt object.
 * @number: a number.
 *
 * Ensure that @extension can use up to @number array buffers. This method
 * should be called only for the current OpenGL context. This should
 * be used only for #VisuGlExt with varying number of
 * array buffers. Otherwise, the number of array buffers should be declared as a
 * class parameter.
 *
 * Since: 3.9
 **/
void visu_gl_ext_bookBuffers(VisuGlExt *self, guint number)
{
  guint nOld;

  g_return_if_fail(VISU_IS_GL_EXT(self));

  if (number <= self->priv->nGlObj)
    return;

  nOld = self->priv->nGlObj;
  self->priv->nGlObj = number;
  self->priv->glBufs = g_realloc(self->priv->glBufs,
                                 sizeof(GLuint) * self->priv->nGlObj);
  glGenBuffers(number - nOld, self->priv->glBufs + nOld);
  self->priv->glVertexArrays = g_realloc(self->priv->glVertexArrays,
                                        sizeof(GLuint) * self->priv->nGlObj);
  glGenVertexArrays(number - nOld, self->priv->glVertexArrays + nOld);
  g_array_set_size(self->priv->glBufMeta, self->priv->nGlObj);
}
/**
 * visu_gl_ext_clearBuffers:
 * @self: a #VisuGlExt object.
 *
 * Empty all buffers of this extension.
 *
 * Since: 3.9
 **/
void visu_gl_ext_clearBuffers(VisuGlExt *self)
{
  guint i;
  
  g_return_if_fail(VISU_IS_GL_EXT(self));

  g_debug("Visu Extension: clearing all buffers.");
  if (self->priv->glBufMeta)
    for (i = 0; i < self->priv->nGlObj; i++)
      g_array_index(self->priv->glBufMeta, struct _BufferMetaData, i).dim = 0;
}
/**
 * visu_gl_ext_setBufferWithAttrib:
 * @self: a #VisuGlExt method.
 * @id: a buffer id.
 * @data: (element-type gfloat): the buffer data.
 * @iProg: a shader id.
 * @attribs: the data layout for the shader.
 *
 * Transfer @data to the GPU.
 *
 * Since: 3.9
 **/
void visu_gl_ext_setBufferWithAttrib(VisuGlExt *self, guint id, const GArray *data,
                                     guint iProg, const VisuGlExtAttrib *attribs)
{
  g_return_if_fail(VISU_IS_GL_EXT(self) && id < self->priv->nGlObj);

  g_debug("Visu Extension: transferring %d buffer (%d) elements to GPU (%d pass).", data ? data->len : 0, id, self->priv->glBufMeta && g_array_index(self->priv->glBufMeta, struct _BufferMetaData, id).count ? g_array_index(self->priv->glBufMeta, struct _BufferMetaData, id).count->len : 0);
  if (self->priv->glBufMeta)
    g_array_index(self->priv->glBufMeta, struct _BufferMetaData, id).dim = data ? data->len : 0;
  if (data && data->len)
    {
      glBindVertexArray(self->priv->glVertexArrays[id]);
      glBindBuffer(GL_ARRAY_BUFFER, self->priv->glBufs[id]);
      glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * data->len, data->data, GL_STATIC_DRAW);
      if (attribs && iProg < self->priv->nGlProg)
        for (guint i = 0; attribs[i].lbl; i++)
          {
            GLint loc = glGetAttribLocation(self->priv->glProgs[iProg].id, attribs[i].lbl);
            g_debug("Visu Extension: getting location for '%s' in shader %d(%d) -> %d",
                    attribs[i].lbl, iProg, self->priv->glProgs[iProg].id, loc);
            g_return_if_fail(loc >= 0);
            glEnableVertexAttribArray(loc);
            glVertexAttribPointer(loc, attribs[i].nEle, attribs[i].kind, GL_FALSE,
                                  attribs[i].stride, attribs[i].offset);
          }
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      glBindVertexArray(0);
    }
}
static VisuGlExtAttrib _xyzAttribs[] =
  {{"position", 3, GL_FLOAT, 3 * sizeof(GLfloat), 0},
   VISU_GL_EXT_NULL_ATTRIB};
static VisuGlExtAttrib _colorAttribs[] =
  {{"position", 3, GL_FLOAT, 7 * sizeof(GLfloat), GINT_TO_POINTER(4 * sizeof(GLfloat))},
   {"color", 4, GL_FLOAT, 7 * sizeof(GLfloat), 0},
   VISU_GL_EXT_NULL_ATTRIB};
static VisuGlExtAttrib _xyzNrmAttribs[] =
  {{"position", 3, GL_FLOAT, 6 * sizeof(GLfloat), 0},
   {"normal", 3, GL_FLOAT, 6 * sizeof(GLfloat), GINT_TO_POINTER(3 * sizeof(GLfloat))},
   VISU_GL_EXT_NULL_ATTRIB};
/**
 * visu_gl_ext_setBuffer:
 * @self: a #VisuGlExt object
 * @id: a buffer id
 * @data: (element-type float): the data to transfer
 *
 * Transfer @data to the GL buffer @id.
 *
 * Since: 3.9
 */
void visu_gl_ext_setBuffer(VisuGlExt *self, guint id, const GArray *data)
{
  g_return_if_fail(VISU_IS_GL_EXT(self) && id < self->priv->nGlObj);

  if (self->priv->glBufMeta)
    {
      struct _BufferMetaData *meta = &g_array_index(self->priv->glBufMeta,
                                                    struct _BufferMetaData, id);
      switch (meta->model)
        {
        case VISU_GL_XYZ:
          visu_gl_ext_setBufferWithAttrib(self, id, data, meta->iProg, _xyzAttribs);
          return;
        case VISU_GL_RGBA_XYZ:
          visu_gl_ext_setBufferWithAttrib(self, id, data, meta->iProg, _colorAttribs);
          return;
        case VISU_GL_XYZ_NRM:
          visu_gl_ext_setBufferWithAttrib(self, id, data, meta->iProg, _xyzNrmAttribs);
          return;
        }
    }
  else
    visu_gl_ext_setBufferWithAttrib(self, id, data, 0, NULL);
}

/**
 * visu_gl_ext_takeBufferWithAttribs:
 * @self: a #VisuGlExt object.
 * @id: a buffer id.
 * @data: (element-type float) (transfer full): some vertice data.
 * @iProg: a program id.
 * @attribs: the data layout.
 *
 * Transfer all CPU data stored in @data into a GPU buffer identified by @id.
 *
 * Since: 3.9
 */
void visu_gl_ext_takeBufferWithAttribs(VisuGlExt *self, guint id, GArray *data,
                                       guint iProg, const VisuGlExtAttrib *attribs)
{
  visu_gl_ext_setBufferWithAttrib(self, id, data, iProg, attribs);
  if (data)
    g_array_unref(data);
}
/**
 * visu_gl_ext_takeBuffer:
 * @self: a #VisuGlExt object.
 * @id: a buffer id.
 * @data: (element-type float) (transfer full): some vertice data.
 *
 * Transfer all CPU data stored in @data into a GPU buffer identified by @id.
 *
 * Since: 3.9
 */
void visu_gl_ext_takeBuffer(VisuGlExt *self, guint id, GArray *data)
{
  visu_gl_ext_setBuffer(self, id, data);
  if (data)
    g_array_unref(data);
}

/**
 * visu_gl_ext_transferBuffer:
 * @self: a #VisuGlExt object.
 * @id: a buffer id.
 * @buffer: (element-type float): some vertice data.
 * @offset: an offset.
 *
 * Transfer @buffer data to the GPU at the position given by @offset
 * in the data buffer allocated for @id.
 *
 * Since: 3.9
 **/
void visu_gl_ext_transferBuffer(const VisuGlExt *self, guint id,
                                const GArray *buffer, guint offset)
{
  g_return_if_fail(VISU_IS_GL_EXT(self) && id < self->priv->nGlObj);

  if (buffer)
    {
      glBindVertexArray(self->priv->glVertexArrays[id]);
      glBindBuffer(GL_ARRAY_BUFFER, self->priv->glBufs[id]);
      glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * buffer->len, buffer->data);
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      glBindVertexArray(0);
    }
}

/**
 * visu_gl_ext_startBuffer:
 * @self: a #VisuGlExt object.
 * @iProg: a shader id.
 * @id: a buffer id.
 * @layout: a #VisuGlExtPackingModels value.
 * @primitive: a #VisuGlExtPrimitives value.
 *
 * Create a new data buffer, identified by @id. The data storage layout in GPU memory
 * is defined by @layout and the prefered rendering scheme is defined by @primitive.
 *
 * Since: 3.9
 *
 * Returns: (transfer full) (element-type float): a new #GArray structure to store
 * the vertice data in CPU memory before transfering them to GPU.
 */
GArray* visu_gl_ext_startBuffer(VisuGlExt *self, guint iProg, guint id,
                                VisuGlExtPackingModels layout, VisuGlExtPrimitives primitive)
{
  struct _BufferMetaData *meta;

  g_return_val_if_fail(VISU_IS_GL_EXT(self) && id < self->priv->nGlObj, (GArray*)0);

  meta = &g_array_index(self->priv->glBufMeta, struct _BufferMetaData, id);
  meta->iProg = MIN(iProg, self->priv->nGlProg);
  meta->model = layout;
  meta->primitive = primitive;
  if (!meta->first)
    meta->first = g_array_new(FALSE, FALSE, sizeof(GLint));
  else
    g_array_set_size(meta->first, 0);
  if (!meta->count)
    meta->count = g_array_new(FALSE, FALSE, sizeof(GLsizei));
  else
    g_array_set_size(meta->count, 0);
  if (!meta->rgbaMat)
    meta->rgbaMat = g_array_new(FALSE, FALSE, sizeof(GLfloat));
  else
    g_array_set_size(meta->rgbaMat, 0);

  return g_array_new(FALSE, FALSE, sizeof(GLfloat));
}
/**
 * visu_gl_ext_layoutBuffer:
 * @self: a #VisuGlExt object
 * @id: a buffer id
 * @data: (element-type float): the data to transfer
 *
 * Count the number of element added to @data with respect to last call. This allow to
 * construct a data buffer with various blocks than need to be drawn together.
 *
 * Since: 3.9
 */
void visu_gl_ext_layoutBuffer(VisuGlExt *self, guint id, const GArray *data)
{
  struct _BufferMetaData *meta;
  guint i;
  GLint first, count;

  g_return_if_fail(VISU_IS_GL_EXT(self) && id < self->priv->nGlObj);

  if (!data || !data->len)
      return;

  meta = &g_array_index(self->priv->glBufMeta, struct _BufferMetaData, id);
  g_return_if_fail(meta->first && meta->count);
  i = meta->first->len;
  first = i ? g_array_index(meta->first, GLint, i - 1) + g_array_index(meta->count, GLsizei, i - 1) : 0;
  g_array_append_val(meta->first, first);
  switch (meta->model)
    {
    case VISU_GL_XYZ:
      count = data->len / 3 - first;
      break;
    case VISU_GL_RGBA_XYZ:
      count = data->len / 7 - first;
      break;
    case VISU_GL_XYZ_NRM:
      count = data->len / 6 - first;
      break;
    }
  g_array_append_val(meta->count, count);
}

/**
 * visu_gl_ext_layoutBufferWithColor:
 * @self: a #VisuGlExt object.
 * @id: a buffer id.
 * @data: (element-type float): some data, using %VISU_GL_XYZ or %VISU_GL_XYZ_NRM
 * packing.
 * @rgba: (array fixed-size=4): some colour definition.
 * @material: (array fixed-size=5): some material definition.
 *
 * Add a new group of vertices defined in @data. Vertices will be rendered using
 * a lighting effect defined by @rgba and @material. If a flat coloured rendering
 * should be used instead, consider using a %VISU_GL_RGBA_XYZ packing instead, so
 * the colour can also be transmitted to the GPU.
 *
 * Since: 3.9
 */
void visu_gl_ext_layoutBufferWithColor(VisuGlExt *self, guint id, const GArray *data,
                                       const gfloat rgba[4], const gfloat material[5])
{
  gboolean diff = TRUE;
  struct _BufferMetaData *meta;

  meta = &g_array_index(self->priv->glBufMeta, struct _BufferMetaData, id);
  g_return_if_fail(meta->rgbaMat);
  if (meta->rgbaMat->len > 0)
    {
      const gfloat *old;
      old = &g_array_index(meta->rgbaMat, gfloat, meta->rgbaMat->len - 9);
      diff = old[0] != rgba[0] || old[1] != rgba[1] || old[2] != rgba[2] || old[3] != rgba[3];
      old = &g_array_index(meta->rgbaMat, gfloat, meta->rgbaMat->len - 5);
      diff = diff || old[0] != material[0] || old[1] != material[1] || old[2] != material[2] || old[3] != material[3] || old[4] != material[4];
    }
  if (diff)
    {
      visu_gl_ext_layoutBuffer(self, id, data);
      g_array_append_vals(meta->rgbaMat, rgba, 4);
      g_array_append_vals(meta->rgbaMat, material, 5);
    }
  else
    {
      GLsizei count;
      switch (meta->model)
        {
        case VISU_GL_XYZ:
          count = data->len / 3;
          break;
        case VISU_GL_RGBA_XYZ:
          count = data->len / 7;
          break;
        case VISU_GL_XYZ_NRM:
          count = data->len / 6;
          break;
        default:
          g_return_if_reached();
        }
      count -= g_array_index(meta->first, GLint, meta->first->len - 1);
      g_array_index(meta->count, GLsizei, meta->count->len - 1) = count;
    }
}

/**
 * visu_gl_ext_startRenderShader:
 * @ext: a #VisuGlExt object.
 * @shaderId: a shader id.
 * @bufId: a buffer id.
 *
 * Load the given @shaderId and bind the data buffer defined by @bufId.
 *
 * Since: 3.9
 **/
void visu_gl_ext_startRenderShader(const VisuGlExt *ext, guint shaderId, guint bufId)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && bufId < ext->priv->nGlObj && shaderId < ext->priv->nGlProg);

  glUseProgram(ext->priv->glProgs[shaderId].id);
  glBindVertexArray(ext->priv->glVertexArrays[bufId]);
  ext->priv->curGlProg = ext->priv->glProgs + shaderId;
  ext->priv->curGlBufMeta = &g_array_index(ext->priv->glBufMeta, struct _BufferMetaData, bufId);
}

/**
 * visu_gl_ext_stopRenderShader:
 * @ext: a #VisuGlExt object.
 * @shaderId: a shader id.
 * @bufId: a buffer id.
 *
 * Stop using the given @shaderId and unbind the data buffer defined by @bufId.
 *
 * Since: 3.9
 **/
void visu_gl_ext_stopRenderShader(const VisuGlExt *ext, guint shaderId, guint bufId)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext) && bufId < ext->priv->nGlObj && shaderId < ext->priv->nGlProg);

  glBindVertexArray(0);
  glUseProgram(0);
  ext->priv->curGlProg = (struct _Prog*)0;
  ext->priv->curGlBufMeta = (struct _BufferMetaData*)0;
}

/**
 * visu_gl_ext_renderBuffers:
 * @ext: a #VisuGlExt object.
 *
 * Render all the declared buffers for @ext.
 *
 * Since: 3.9
 */
void visu_gl_ext_renderBuffers(const VisuGlExt *ext)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext));

  for (guint i = 0; i < ext->priv->nGlObj; i++)
    visu_gl_ext_renderBuffer(ext, i);
}

/**
 * visu_gl_ext_renderBuffer:
 * @ext: a #VisuGlExt object.
 * @id: a buffer id.
 *
 * Render the buffer identified by @id for @ext.
 *
 * Since: 3.9
 */
void visu_gl_ext_renderBuffer(const VisuGlExt *ext, guint id)
{
  struct _BufferMetaData *meta;
  g_return_if_fail(VISU_IS_GL_EXT(ext) && id < ext->priv->nGlObj);
  
  meta = &g_array_index(ext->priv->glBufMeta, struct _BufferMetaData, id);
  visu_gl_ext_renderBufferWithPrimitive(ext, id, meta->primitive, (const gfloat*)0);
}

/**
 * visu_gl_ext_renderBufferWithPrimitive:
 * @ext: a #VisuGlExt object.
 * @id: a buffer id.
 * @primitive: a #VisuGlExtPrimitives value.
 * @rgba: (allow-none): overriding color.
 *
 * Render the buffer identified by @id, overloading its own rendering
 * primitive with @primitive. If @rgba is provided this colour
 * is used instead of the vertice colors.
 *
 * Since: 3.9
 */
void visu_gl_ext_renderBufferWithPrimitive(const VisuGlExt *ext, guint id,
                                           VisuGlExtPrimitives primitive,
                                           const gfloat *rgba)
{
  struct _BufferMetaData *meta;
  guint div, i;
  gboolean stop = TRUE;
  
  g_return_if_fail(VISU_IS_GL_EXT(ext) && id < ext->priv->nGlObj);

  meta = &g_array_index(ext->priv->glBufMeta, struct _BufferMetaData, id);
  g_debug("Visu Extension: %s is rendering %d data.", ext->priv->name, meta->dim);
  if (!meta->dim)
    return;

  g_return_if_fail(meta->iProg < ext->priv->nGlProg);
  if (!ext->priv->curGlProg)
    visu_gl_ext_startRenderShader(ext, meta->iProg, id);
  else
    {
      if (ext->priv->curGlBufMeta != meta)
        {
          glBindVertexArray(ext->priv->glVertexArrays[id]);
          ext->priv->curGlBufMeta = meta;
        }
      stop = FALSE;
    }
  if (rgba)
    visu_gl_ext_setUniformRGBA(ext, rgba);
  switch (meta->model)
    {
    case VISU_GL_XYZ:
      div = 3;
      break;
    case VISU_GL_RGBA_XYZ:
      div = 7;
      break;
    case VISU_GL_XYZ_NRM:
      div = 6;
      break;
    default:
      g_return_if_reached();
    }
  if (meta->rgbaMat && meta->rgbaMat->len && !rgba)
    for (i = 0; i < meta->rgbaMat->len / 9; i++)
      {
        if (g_array_index(meta->rgbaMat, GLfloat, i * 9 + 4) < 0)
          glUniform3uiv(ext->priv->curGlProg->uniforms[UNIFORM_COLOR], 1,
                        &g_array_index(meta->rgbaMat, GLuint, i * 9));
        else
          {
            visu_gl_ext_setUniformRGBA(ext, &g_array_index(meta->rgbaMat, GLfloat, i * 9));
            visu_gl_ext_setUniformMaterial(ext, &g_array_index(meta->rgbaMat, GLfloat, i * 9 + 4));
          }
        glDrawArrays(_primitives[primitive], g_array_index(meta->first, GLint, i),
                     g_array_index(meta->count, GLsizei, i));
      }
  else if (meta->first && meta->first->len)
    glMultiDrawArrays(_primitives[primitive], (GLint*)meta->first->data,
                      (GLsizei*)meta->count->data, meta->first->len);
  else
    glDrawArrays(_primitives[primitive], 0, meta->dim / div);
  if (stop)
    visu_gl_ext_stopRenderShader(ext, meta->iProg, id);
}

static void _setTexBuffer(VisuGlExt *self)
{
  GError *error = (GError*)0;
  GLint loc;

  if (self->priv->texProg.id)
    {
      glDeleteProgram(self->priv->texProg.id);
      self->priv->texProg.id = 0;
    }
  if (self->priv->texVertexArray)
    {
      glDeleteVertexArrays(1, &self->priv->texVertexArray);
      self->priv->texVertexArray = 0;
    }
  if (!self->priv->nGlTex)
    return;

  if (!tool_gl_createTextShader(&self->priv->texProg.id, self->priv->texProg.uniforms,
                                self->priv->withFog, &error))
    {
      g_warning("cannot compile texture shaders: %s", error->message);
      g_clear_error(&error);
      return;
    }

  glGenVertexArrays(1, &self->priv->texVertexArray);
  glBindVertexArray(self->priv->texVertexArray);

  glGenBuffers(1, &self->priv->glTexBuf);
  glBindBuffer(GL_ARRAY_BUFFER, self->priv->glTexBuf);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vTex), vTex, GL_STATIC_DRAW);

  loc = glGetAttribLocation(self->priv->texProg.id, "texPosition");
  g_return_if_fail(loc >= 0);
  glEnableVertexAttribArray(loc);
  glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
  loc = glGetAttribLocation(self->priv->texProg.id, "texCoord");
  g_return_if_fail(loc >= 0);
  glEnableVertexAttribArray(loc);
  glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), GINT_TO_POINTER(2 * sizeof(GLfloat)));

  glBindBuffer(GL_ARRAY_BUFFER, 0);

  glBindVertexArray(0);
}

/**
 * visu_gl_ext_addTextures:
 * @self: a #VisuGlExt object.
 * @number: a number.
 *
 * Create @number new available 2D textures. This method
 * should be called only for the current OpenGL context. This should
 * be used only for #VisuGlExt with varying number of
 * textures. Otherwise, the number of textures should be declared as a
 * constructor parameter.
 *
 * Since: 3.9
 *
 * Returns: the index to be used in visu_gl_ext_blitTextureAt() for
 * instance, or %G_MAXUINT on error.
 **/
guint visu_gl_ext_addTextures(VisuGlExt *self, guint number)
{
  guint nOld;

  g_return_val_if_fail(VISU_IS_GL_EXT(self), G_MAXUINT);

  if (!number)
    return G_MAXUINT;

  nOld = self->priv->nGlTex;
  self->priv->nGlTex += number;
  self->priv->glTexs = g_realloc(self->priv->glTexs,
                                 sizeof(GLuint) * self->priv->nGlTex);
  self->priv->glTexDims = g_realloc(self->priv->glTexDims,
                                    sizeof(GLuint) * self->priv->nGlTex * 2);
  glGenTextures(number, self->priv->glTexs + nOld);

  return nOld;
}
/**
 * visu_gl_ext_bookTextures:
 * @self: a #VisuGlExt object.
 * @number: a number.
 *
 * Ensure that @extension can use up to @number 2D textures. This method
 * should be called only for the current OpenGL context. This should
 * be used only for #VisuGlExt with varying number of
 * textures. Otherwise, the number of textures should be declared as a
 * constructor parameter.
 *
 * Since: 3.9
 **/
void visu_gl_ext_bookTextures(VisuGlExt *self, guint number)
{
  guint nOld;

  g_return_if_fail(VISU_IS_GL_EXT(self));

  if (number <= self->priv->nGlTex)
    return;

  nOld = self->priv->nGlTex;
  self->priv->nGlTex = number;
  self->priv->glTexs = g_realloc(self->priv->glTexs,
                                 sizeof(GLuint) * self->priv->nGlTex);
  self->priv->glTexDims = g_realloc(self->priv->glTexDims,
                                    sizeof(GLuint) * self->priv->nGlTex * 2);
  glGenTextures(number - nOld, self->priv->glTexs + nOld);
  if (!nOld && self->priv->nGlTex)
    _setTexBuffer(self);
}
/**
 * visu_gl_ext_clearLabels:
 * @self: a #VisuGlExt object.
 *
 * Remove all labels displayed by this extension.
 *
 * Since: 3.9
 **/
void visu_gl_ext_clearLabels(VisuGlExt *self)
{
  g_return_if_fail(VISU_IS_GL_EXT(self));

  if (self->priv->labels)
    g_array_unref(self->priv->labels);
  self->priv->labels = (GArray*)0;
}

/**
 * visu_gl_ext_setLabels:
 * @self: a #VisuGlExt object.
 * @labels: (element-type VisuGlExtLabel): a set of labels.
 * @size: the font size.
 * @stroke: a boolean.
 *
 * Store a set of texts to be drawn in the scene at given positions with
 * given colours. These labels will be rendered using the given @size
 * and maybe outlined by a black stroke.
 *
 * Since: 3.9
 */
void visu_gl_ext_setLabels(VisuGlExt *self, GArray *labels,
                           ToolWriterFontSize size, gboolean stroke)
{
  g_return_if_fail(VISU_IS_GL_EXT(self));

  visu_gl_ext_clearLabels(self);
  if (labels && labels->len)
    {
      ToolWriter *writer = tool_writer_getStatic();
      guint i;
      self->priv->labels = g_array_ref(labels);
      visu_gl_ext_bookTextures(self, labels->len);
      tool_writer_setSize(writer, size);
      tool_writer_setStroke(writer, stroke);
      for (i = 0; i < labels->len; i++)
        {
          VisuGlExtLabel *label = &g_array_index(labels, VisuGlExtLabel, i);
          GArray *buf = tool_writer_layout(writer, label->lbl, &label->width, &label->height);
          visu_gl_ext_setTexture2D(self, i, (const unsigned char*)buf->data,
                                   label->width, label->height);
          g_array_unref(buf);
        }
      tool_writer_setStroke(writer, FALSE);
      g_object_unref(writer);
    }
}
/**
 * visu_gl_ext_setTexture2D:
 * @extension: a #VisuGlExt object.
 * @texId: an id for the texture.
 * @pixels: some data.
 * @width: the width.
 * @height: the height.
 *
 * Upload @pixels to the graphic card in the texture registered as @texId.
 * The @texId must be within 0 and nGlTex property value. The storage of
 * @pixels must be ARGB unsigned bytes.
 *
 * Since: 3.9
 **/
void visu_gl_ext_setTexture2D(const VisuGlExt *extension, guint texId,
                              const unsigned char *pixels, guint width, guint height)
{
  g_return_if_fail(VISU_IS_GL_EXT(extension));
  g_return_if_fail(texId < extension->priv->nGlTex);
  g_return_if_fail(extension->priv->glTexs);

  glBindTexture(GL_TEXTURE_2D, extension->priv->glTexs[texId]);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
               GL_BGRA, GL_UNSIGNED_BYTE, pixels);
  extension->priv->glTexDims[texId * 2 + 0] = width;
  extension->priv->glTexDims[texId * 2 + 1] = height;
  glBindTexture(GL_TEXTURE_2D, 0);
}
/**
 * visu_gl_ext_setImage:
 * @extension: a #VisuGlExt object.
 * @texId: an id for the texture.
 * @pixels: some data.
 * @width: the width.
 * @height: the height.
 * @hasAlpha: if the image is coded in RGB or RGBA.
 *
 * Upload @pixels to the graphic card in the texture registered as @texId.
 * The @texId must be within 0 and nGlTex property value. The storage of
 * @pixels must be RGB or RGBA unsigned bytes.
 *
 * Since: 3.9
 **/
void visu_gl_ext_setImage(const VisuGlExt *extension, guint texId,
                          const unsigned char *pixels, guint width, guint height, gboolean hasAlpha)
{
  g_return_if_fail(VISU_IS_GL_EXT(extension));
  g_return_if_fail(texId < extension->priv->nGlTex);

  glBindTexture(GL_TEXTURE_2D, extension->priv->glTexs[texId]);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexImage2D(GL_TEXTURE_2D, 0, hasAlpha ? GL_RGBA : GL_RGB, width, height, 0,
               hasAlpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, pixels);
  extension->priv->glTexDims[texId * 2 + 0] = width;
  extension->priv->glTexDims[texId * 2 + 1] = height;
  glBindTexture(GL_TEXTURE_2D, 0);
}

static void _blitTexture(const VisuGlExt *extension, guint texId)
{
  glBindTexture(GL_TEXTURE_2D, extension->priv->glTexs[texId]);
  glDisable(GL_CULL_FACE);
  glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
  glEnable(GL_CULL_FACE);
  glBindTexture(GL_TEXTURE_2D, 0);
}

/**
 * visu_gl_ext_blitLabels:
 * @extension: a #VisuGlExt object
 *
 * Draw all labels at the stored position in the scene. See visu_gl_ext_setLabels().
 *
 * Since: 3.9
 */
void visu_gl_ext_blitLabels(const VisuGlExt *extension)
{
  guint i;
  gfloat rgba[4] = {G_MAXFLOAT, 0.f, 0.f, 0.f};

  g_return_if_fail(VISU_IS_GL_EXT(extension));

  if (!extension->priv->labels || !extension->priv->labels->len)
    return;
  
  for (i = 0; i < extension->priv->labels->len; i++)
    {
      VisuGlExtLabel *lbl = &g_array_index(extension->priv->labels, VisuGlExtLabel, i);
      if (lbl->rgba[0] != rgba[0] || lbl->rgba[1] != rgba[1] ||
          lbl->rgba[2] != rgba[2] || lbl->rgba[3] != rgba[3])
        {
          visu_gl_ext_setUniformTexRGBA(extension, lbl->rgba);
          memcpy(rgba, lbl->rgba, sizeof(gfloat) * 4);
        }
      
      g_debug("Visu Extension: bliting '%s' at %g x %g x %g.",
              lbl->lbl, lbl->xyz[0], lbl->xyz[1], lbl->xyz[2]);
      visu_gl_ext_blitTextureAt(extension, i, lbl->xyz, lbl->offset);
    }
}

/**
 * visu_gl_ext_blitLabelsOnScreen:
 * @extension: a #VisuGlExt object
 *
 * Draw all labels at the stored position on the screen. See visu_gl_ext_setLabels().
 *
 * Since: 3.9
 */
void visu_gl_ext_blitLabelsOnScreen(const VisuGlExt *extension)
{
  guint i;
  gfloat rgba[4] = {G_MAXFLOAT, 0.f, 0.f, 0.f};

  g_return_if_fail(VISU_IS_GL_EXT(extension));

  if (!extension->priv->labels || !extension->priv->labels->len)
    return;
  
  for (i = 0; i < extension->priv->labels->len; i++)
    {
      VisuGlExtLabel *lbl = &g_array_index(extension->priv->labels, VisuGlExtLabel, i);
      if (lbl->rgba[0] != rgba[0] || lbl->rgba[1] != rgba[1] ||
          lbl->rgba[2] != rgba[2] || lbl->rgba[3] != rgba[3])
        {
          visu_gl_ext_setUniformTexRGBA(extension, lbl->rgba);
          memcpy(rgba, lbl->rgba, sizeof(gfloat) * 4);
        }

      g_debug("Visu Extension: bliting '%s' at %g x %g.", lbl->lbl, lbl->xyz[0], lbl->xyz[1]);
      visu_gl_ext_blitTextureOnScreen(extension, i, lbl->xyz,
                                      lbl->align[0], lbl->align[1]);
    }
}
/**
 * visu_gl_ext_blitTextureAt:
 * @extension: a #VisuGlExt object.
 * @texId: a texture id.
 * @at: 3 coordinates in cartesian space.
 * @offset: 3 coordinates in modelview space.
 *
 * Displays the texture referred by @texId at a position in the scene
 * given by @at. The offset is applied in model view space, making it
 * independant of the camera position.
 *
 * Since: 3.9
 **/
void visu_gl_ext_blitTextureAt(const VisuGlExt *extension, guint texId,
                               const gfloat at[3], const gfloat offset[3])
{
  const gfloat screen[2] = {0.5, 0.5}, align[2] = {0., 0.};

  g_return_if_fail(VISU_IS_GL_EXT(extension));
  g_return_if_fail(texId < extension->priv->nGlTex);

  glUniform3fv(extension->priv->texProg.uniforms[UNIFORM_OFFSET], 1, offset);
  glUniform3fv(extension->priv->texProg.uniforms[UNIFORM_TEXT_XYZ_POS], 1, at);
  glUniform2fv(extension->priv->texProg.uniforms[UNIFORM_TEXT_RED_POS], 1, screen);
  glUniform2fv(extension->priv->texProg.uniforms[UNIFORM_TEXT_ALIGN], 1, align);
  glUniform2uiv(extension->priv->texProg.uniforms[UNIFORM_TEXT_DIM], 1, extension->priv->glTexDims + (2 * texId));
  _blitTexture(extension, texId);
}

/**
 * visu_gl_ext_blitTextureOnScreen:
 * @extension: a #VisuGlExt object.
 * @texId: a texture id.
 * @at: two coordinates in [0.;1.].
 * @hAlign: horizontal alignment.
 * @vAlign: vertical alignment.
 *
 * Draw the texture referred by @texId at the position @at on
 * screen. The texture is positioned on @at.
 *
 * Since: 3.9
 **/
void visu_gl_ext_blitTextureOnScreen(const VisuGlExt *extension, guint texId, const gfloat at[2], VisuGlExtAlignments hAlign, VisuGlExtAlignments vAlign)
{
  const GLfloat off[] = {0.f, .5f, 1.f};
  const gfloat pos[3] = {0.f, 0.f, 0.f}, align[2] = {off[hAlign], off[vAlign]}, offset[3] = {0., 0., 0.};

  g_return_if_fail(VISU_IS_GL_EXT(extension));
  g_return_if_fail(texId < extension->priv->nGlTex);

  glUniform3fv(extension->priv->texProg.uniforms[UNIFORM_OFFSET], 1, offset);
  glUniform3fv(extension->priv->texProg.uniforms[UNIFORM_TEXT_XYZ_POS], 1, pos);
  glUniform2fv(extension->priv->texProg.uniforms[UNIFORM_TEXT_RED_POS], 1, at);
  glUniform2fv(extension->priv->texProg.uniforms[UNIFORM_TEXT_ALIGN], 1, align);
  glUniform2uiv(extension->priv->texProg.uniforms[UNIFORM_TEXT_DIM], 1, extension->priv->glTexDims + (2 * texId));
  _blitTexture(extension, texId);
}
/**
 * visu_gl_ext_adjustTextureDimensions:
 * @ext: a #VisuGlExt object.
 * @texId: a texture id.
 * @width: a width value in pixels.
 * @height: a height value in pixels.
 *
 * Adjust texture rendering dimension by hand. By default, textures are not scaled.
 *
 * Since: 3.9
 */
void visu_gl_ext_adjustTextureDimensions(const VisuGlExt *ext, guint texId,
                                         gfloat width, gfloat height)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext));
  g_return_if_fail(texId < ext->priv->nGlTex);

  ext->priv->glTexDims[2 * texId + 0] = width;
  ext->priv->glTexDims[2 * texId + 1] = height;
}

/****************/
/* Private area */
/****************/
static void _callList(const VisuGlExt *ext, const VisuGlExtGlobals *globals,
                      VisuGlRenderingMode *renderingMode, VisuGlRenderingMode globalRenderingMode)
{
#if DEBUG == 1
  GTimer *timer;
  gulong fractionTimer;

  timer = g_timer_new();
  g_timer_start(timer);
#endif

  /* The extension needs its own rendering mode. */
  if (ext->priv->preferedRenderingMode < VISU_GL_RENDERING_N_MODES)
    {
      if (ext->priv->preferedRenderingMode != *renderingMode)
        {
          visu_gl_rendering_applyMode(ext->priv->preferedRenderingMode);
          *renderingMode = ext->priv->preferedRenderingMode;
        }
    }
  else
    {
      if (*renderingMode != globalRenderingMode)
        {
          visu_gl_rendering_applyMode(globalRenderingMode);
          *renderingMode = globalRenderingMode;
        }
    }

  if (ext->priv->preferedRenderingMode < VISU_GL_RENDERING_N_MODES &&
      *renderingMode == VISU_GL_RENDERING_SMOOTH_AND_EDGE)
    {
      glEnable(GL_POLYGON_OFFSET_FILL);
      glPolygonOffset(1.0, 1.0);
    }

  /* Call the compiled list. */
#if DEBUG == 1
  g_debug("Visu Extension: render (%s %d) at %g micro-s.",
          ext->priv->name, ext->priv->used, g_timer_elapsed(timer, &fractionTimer) * 1e6);
#endif
  if (globals)
    for (guint i = 0; i < ext->priv->nGlProg; i++)
      {
        const GLfloat zero[3] = {0.f, 0.f, 0.f};
        if (!ext->priv->glProgs[i].id)
          continue;
        glUseProgram(ext->priv->glProgs[i].id);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_MVP] >= 0)
          glUniformMatrix4fv(ext->priv->glProgs[i].uniforms[UNIFORM_MVP], 1, GL_FALSE, globals->mvp.c[0]);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_MV] >= 0)
          glUniformMatrix4fv(ext->priv->glProgs[i].uniforms[UNIFORM_MV], 1, GL_FALSE, globals->MV.c[0]);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_TRANSLATION] >= 0)
          glUniform3fv(ext->priv->glProgs[i].uniforms[UNIFORM_TRANSLATION], 1, zero);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_RESOLUTION] >= 0)
          glUniform2fv(ext->priv->glProgs[i].uniforms[UNIFORM_RESOLUTION], 1, globals->resolution);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_FOG] >= 0)
          glUniform2fv(ext->priv->glProgs[i].uniforms[UNIFORM_FOG], 1, globals->fogStartEnd);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_FOG_COLOR] >= 0)
          glUniform4fv(ext->priv->glProgs[i].uniforms[UNIFORM_FOG_COLOR], 1, globals->fogColor);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_CAMERA_POS] >= 0)
          glUniform3fv(ext->priv->glProgs[i].uniforms[UNIFORM_CAMERA_POS], 1, globals->camPosition);
        if (ext->priv->glProgs[i].uniforms[UNIFORM_LIGHT_POS] >= 0)
          {
            GList *lights = visu_gl_lights_getList(visu_gl_getLights(ext->priv->gl));
            VisuGlLight *light = lights ? (VisuGlLight*)lights->data : (VisuGlLight*)0;
            if (light)
              glUniform3fv(ext->priv->glProgs[i].uniforms[UNIFORM_LIGHT_POS], 1, light->position);
          }
        glUseProgram(0);
      }
  if (VISU_GL_EXT_GET_CLASS(ext)->render)
    VISU_GL_EXT_GET_CLASS(ext)->render(ext);

  /* Add a wireframe draw if renderingMode is VISU_GL_RENDERING_SMOOTH_AND_EDGE. */
  if (VISU_GL_EXT_GET_CLASS(ext)->render &&
      ext->priv->preferedRenderingMode < VISU_GL_RENDERING_N_MODES &&
      *renderingMode == VISU_GL_RENDERING_SMOOTH_AND_EDGE)
    {
      glDisable(GL_POLYGON_OFFSET_FILL);
      glLineWidth(1);
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      VISU_GL_EXT_GET_CLASS(ext)->render(ext);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

#if DEBUG == 1
  g_timer_stop(timer);
  g_timer_destroy(timer);
#endif
}

/**
 * visu_gl_ext_setUniformTexRGBA:
 * @ext: a #VisuGlExt object.
 * @rgba: (array fixed-size=4): a color.
 *
 * Send @rgba to the GPU for the texture shader.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformTexRGBA(const VisuGlExt *ext, const gfloat rgba[4])
{
  g_return_if_fail(VISU_IS_GL_EXT(ext));
  
  glUniform4fv(ext->priv->texProg.uniforms[UNIFORM_COLOR], 1, rgba);
}
/**
 * visu_gl_ext_setUniformTexResolution:
 * @ext: a #VisuGlExt object.
 * @w: a width.
 * @h: a height.
 *
 * Send (@w,@h) to the GPU for the texture shader.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformTexResolution(const VisuGlExt *ext, guint w, guint h)
{
  gfloat res[2] = {w, h};

  g_return_if_fail(VISU_IS_GL_EXT(ext));
  
  glUniform2fv(ext->priv->texProg.uniforms[UNIFORM_RESOLUTION], 1, res);
}
/**
 * visu_gl_ext_setUniformTexMVP:
 * @ext: a #VisuGlExt object.
 * @MVP: a #ToolGlMatrix object.
 *
 * Send @MVP to the GPU for the texture shader.
 *
 * Since: 3.9
 */
void visu_gl_ext_setUniformTexMVP(const VisuGlExt *ext, const ToolGlMatrix *MVP)
{
  g_return_if_fail(VISU_IS_GL_EXT(ext));
  
  if (ext->priv->texProg.uniforms[UNIFORM_MVP] >= 0 && MVP)
    glUniformMatrix4fv(ext->priv->texProg.uniforms[UNIFORM_MVP], 1, GL_FALSE, MVP->c[0]);
}

/**
 * visu_gl_ext_cullFace:
 * @ext: a #VisuGlExt object.
 * @status: a boolean.
 *
 * Render or not polygons pointing inside.
 *
 * Since: 3.9
 */
void visu_gl_ext_cullFace(const VisuGlExt *ext _U_, gboolean status)
{
  if (status)
    glEnable(GL_CULL_FACE);
  else
    glDisable(GL_CULL_FACE);
}

static void onEntryMode(VisuGlExt *ext, VisuConfigFileEntry *entry, VisuConfigFile *obj _U_)
{
  if (g_strcmp0(ext->priv->name, visu_config_file_entry_getLabel(entry)))
    return;

  visu_gl_ext_setPreferedRenderingMode(ext, _rMode);
}
static void exportRendering(GString *data, VisuData *dataObj _U_)
{
  GList *tmp;
  VisuGlExt *ext;
  const char **names;

  if (!my_class)
    g_type_class_ref(VISU_TYPE_GL_EXT);

  visu_config_file_exportComment(data, DESC_RESOURCE_MODE);

  names = visu_gl_rendering_getAllModes();
  for (tmp = my_class->allExtensions; tmp; tmp = g_list_next(tmp))
    {
      ext = (VisuGlExt*)tmp->data;
      if (ext->priv->preferedRenderingMode < VISU_GL_RENDERING_N_MODES)
        visu_config_file_exportEntry(data, FLAG_RESOURCE_MODE, ext->priv->name,
                                     "%s", names[ext->priv->preferedRenderingMode]);
    }
  visu_config_file_exportComment(data, "");
}
