/* -*- mode: C; c-basic-offset: 2 -*- */
/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2022)
  
	Adresse 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 : Damien
	CALISTE, laboratoire L_Sim, (2022)

	E-mail address:
	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 "toolGl.h"

#include <epoxy/gl.h>

/**
 * SECTION:toolGl
 * @short_description: Defines basic OpenGL wrappers to handle common shaders.
 *
 * <para>Define some commonly used shaders.</para>
 */

static GQuark quark = 0;
/**
 * tool_gl_getErrorQuark: (skip)
 *
 * Get error #GQuark for #ToolGl.
 *
 * Since: 3.9
 */
GQuark tool_gl_getErrorQuark()
{
  if (!quark)
    quark =  g_quark_from_static_string("ToolGl");
  return quark;
}

/**
 * tool_gl_createShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @vertexSource: some GLSL code for vertices.
 * @fragmentSource: some GLSL code for fragments.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program using @vertexSource and @fragmentSource. On
 * success, the GL id for the program is stored in @id.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createShader(guint *id, const char *vertexSource, const char *fragmentSource, GError **error)
{
  int status = 0;
  guint vertex = 0, fragment = 0;

  vertex = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertex, 1, &vertexSource, NULL);
  glCompileShader(vertex);

  glGetShaderiv(vertex, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
    {
      char *buffer;
      int log_len = 0;
      glGetShaderiv(vertex, GL_INFO_LOG_LENGTH, &log_len);
      buffer = g_malloc(log_len + 1);
      glGetShaderInfoLog(vertex, log_len, NULL, buffer);
      buffer[log_len] = '\0';

      g_set_error (error, TOOL_GL_ERROR, ERROR_VERTEX_COMPILATION,
                   "Compilation failure in vertex shader: %s", buffer);

      g_free (buffer);
      glDeleteShader(vertex);

      return FALSE;
    }

  fragment = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragment, 1, &fragmentSource, NULL);
  glCompileShader(fragment);

  glGetShaderiv(fragment, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
    {
      char *buffer;
      int log_len = 0;
      glGetShaderiv(fragment, GL_INFO_LOG_LENGTH, &log_len);
      buffer = g_malloc(log_len + 1);
      glGetShaderInfoLog(fragment, log_len, NULL, buffer);
      buffer[log_len] = '\0';

      g_set_error (error, TOOL_GL_ERROR, ERROR_FRAGMENT_COMPILATION,
                   "Compilation failure in fragment shader: %s", buffer);

      g_free(buffer);
      glDeleteShader(vertex);
      glDeleteShader(fragment);

      return FALSE;
    }

  *id = glCreateProgram();
  glAttachShader(*id, vertex);
  glAttachShader(*id, fragment);
  glLinkProgram(*id);
  glGetProgramiv(*id, GL_LINK_STATUS, &status);
  if (status == GL_FALSE)
    {
      char *buffer;
      int log_len = 0;
      glGetProgramiv(*id, GL_INFO_LOG_LENGTH, &log_len);
      buffer = g_malloc(log_len + 1);
      glGetProgramInfoLog(*id, log_len, NULL, buffer);
      buffer[log_len] = '\0';

      g_set_error(error, TOOL_GL_ERROR, ERROR_PROGRAM_LINK,
                  "Linking failure in program: %s", buffer);

      g_free(buffer);
      glDeleteShader(vertex);
      glDeleteShader(fragment);
      glDeleteProgram(*id);
      *id = 0;

      return FALSE;
    }

  glDetachShader(*id, vertex);
  glDetachShader(*id, fragment);

  glDeleteShader(vertex);
  glDeleteShader(fragment);

  return TRUE;
}

/**
 * tool_gl_createFlatShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @locs: (out) (array fixed-size=N_UNIFORMS): an array to store possible uniform ids.
 * @withFog: a boolean.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program rendering flat colours independant on the vertices. On
 * success, the GL id for the program is stored in @id. It will be sensitive to fog
 * according to @withFog.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createFlatShader(guint *id, gint locs[N_UNIFORMS], gboolean withFog, GError **error)
{
  const char *vertex =
    "#version 130\n"
    "in vec3 position;\n"
    "uniform mat4 mvp;\n"
    "out float z;\n"
    "\n"
    "void main() {\n"
    "  gl_Position = mvp * vec4(position, 1.0);\n"
    "  z = gl_Position.z / gl_Position.w;\n"
     "}";
  const char *fogFragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec2 fog;\n"
    "uniform vec4 fogColor;\n"
    "uniform vec4 color;\n"
    "in float z;\n"
    "\n"
    "void main() {\n"
    "  float f = clamp((z * 0.5 - fog.x + 0.5) / (fog.y - fog.x), 0., 1.);\n"
    "  outputColor = vec4(color.xyz * (1 - f) + fogColor.xyz * f, color.w);\n"
    "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec4 color;\n"
    "\n"
    "void main() {\n"
    "  outputColor = color;\n"
    "}";

  if (!tool_gl_createShader(id, vertex, withFog ? fogFragment : fragment, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    locs[i] = -1;
  locs[UNIFORM_MVP] = glGetUniformLocation(*id, "mvp");
  locs[UNIFORM_COLOR] = glGetUniformLocation(*id, "color");
  if (withFog)
    {
      locs[UNIFORM_FOG] = glGetUniformLocation(*id, "fog");
      locs[UNIFORM_FOG_COLOR] = glGetUniformLocation(*id, "fogColor");
    }

  g_debug("Tool GL: created flat shader %d.", *id);
  return TRUE;
}

/**
 * tool_gl_createSimpleShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @locs: (out) (array fixed-size=N_UNIFORMS): an array to store possible uniform ids.
 * @withFog: a boolean.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program rendering colours which depend on the vertices. On
 * success, the GL id for the program is stored in @id. It will be sensitive to fog
 * according to @withFog.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createSimpleShader(guint *id, gint locs[N_UNIFORMS], gboolean withFog, GError **error)
{
  const char *vertex =
    "#version 130\n"
    "in vec3 position;\n"
    "in vec4 color;\n"
    "uniform mat4 mvp;\n"
    "out float z;\n"
    "out vec4 vertColor;\n"
    "\n"
    "void main() {\n"
    "  gl_Position = mvp * vec4(position, 1.0);\n"
    "  z = gl_Position.z / gl_Position.w;\n"
    "  vertColor = color;\n"
     "}";
  const char *fogFragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec2 fog;\n"
    "uniform vec4 fogColor;\n"
    "in float z;\n"
    "in vec4 vertColor;\n"
    "\n"
    "void main() {\n"
    "  float f = clamp((z * 0.5 - fog.x + 0.5) / (fog.y - fog.x), 0., 1.);\n"
    "  outputColor = vec4(vertColor.xyz * (1 - f) + fogColor.xyz * f, vertColor.w);\n"
    "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "in vec4 vertColor;\n"
    "\n"
    "void main() {\n"
    "  outputColor = vertColor;\n"
    "}";

  if (!tool_gl_createShader(id, vertex, withFog ? fogFragment : fragment, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    locs[i] = -1;
  locs[UNIFORM_MVP] = glGetUniformLocation(*id, "mvp");
  if (withFog)
    {
      locs[UNIFORM_FOG] = glGetUniformLocation(*id, "fog");
      locs[UNIFORM_FOG_COLOR] = glGetUniformLocation(*id, "fogColor");
    }

  g_debug("Tool GL: created simple shader %d.", *id);
  return TRUE;
}

/**
 * tool_gl_createLineShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @locs: (out) (array fixed-size=N_UNIFORMS): an array to store possible uniform ids.
 * @withFog: a boolean.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program rendering lines with fixed colours. On
 * success, the GL id for the program is stored in @id. It will be sensitive to fog
 * according to @withFog.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createLineShader(guint *id, gint locs[N_UNIFORMS], gboolean withFog, GError **error)
{
  const char *vertex =
    "#version 130\n"
    "in vec3 position;\n"
    "uniform mat4 mvp;\n"
    "flat out vec3 startPos;\n"
    "out vec3 vertPos;\n"
    "\n"
    "void main() {\n"
    "  vec4 pos    = mvp * vec4(position, 1.0);\n"
    "  gl_Position = pos;\n"
    "  vertPos     = pos.xyz / pos.w;\n"
    "  startPos    = vertPos;\n"
    "}";
  const char *fogFragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec4 color;\n"
    "uniform vec2 resolution;\n"
    "uniform uint stipple;\n"
    "uniform vec2 fog;\n"
    "uniform vec4 fogColor;\n"
    "flat in vec3 startPos;\n"
    "in vec3 vertPos;\n"
    "\n"
    "void main() {\n"
    "  vec2  dir  = (vertPos.xy - startPos.xy) * resolution / 2.0;\n"
    "  float dist = length(dir);\n"
    "  uint bit = uint(round(dist)) & 15U;\n"
    "  if ((stipple & (1U<<bit)) == 0U)\n"
    "    discard; \n"
    "  float f = clamp((vertPos.z * 0.5 - fog.x + 0.5) / (fog.y - fog.x), 0., 1.);\n"
    "  outputColor = color * (1 - f) + fogColor * f;\n"
    "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec4 color;\n"
    "uniform vec2 resolution;\n"
    "uniform uint stipple;\n"
    "flat in vec3 startPos;\n"
    "in vec3 vertPos;\n"
    "\n"
    "void main() {\n"
    "  vec2  dir  = (vertPos.xy - startPos.xy) * resolution / 2.0;\n"
    "  float dist = length(dir);\n"
    "  uint bit = uint(round(dist)) & 15U;\n"
    "  if ((stipple & (1U<<bit)) == 0U)\n"
    "    discard; \n"
    "  outputColor = color;\n"
    "}";

  if (!tool_gl_createShader(id, vertex, withFog ? fogFragment : fragment, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    locs[i] = -1;
  locs[UNIFORM_MVP] = glGetUniformLocation(*id, "mvp");
  locs[UNIFORM_RESOLUTION] = glGetUniformLocation(*id, "resolution");
  locs[UNIFORM_COLOR] = glGetUniformLocation(*id, "color");
  locs[UNIFORM_STIPPLE] = glGetUniformLocation(*id, "stipple");
  if (withFog)
    {
      locs[UNIFORM_FOG] = glGetUniformLocation(*id, "fog");
      locs[UNIFORM_FOG_COLOR] = glGetUniformLocation(*id, "fogColor");      
    }

  g_debug("Tool GL: created line shader %d (fog: %d).", *id, withFog);
  return TRUE;
}

/**
 * tool_gl_createColoredLineShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @locs: (out) (array fixed-size=N_UNIFORMS): an array to store possible uniform ids.
 * @withFog: a boolean.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program rendering lines which colours depend on the vertices. On
 * success, the GL id for the program is stored in @id. It will be sensitive to fog
 * according to @withFog.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createColoredLineShader(guint *id, gint locs[N_UNIFORMS], gboolean withFog, GError **error)
{
  const char *vertex =
    "#version 130\n"
    "in vec3 position;\n"
    "in vec4 color;\n"
    "uniform mat4 mvp;\n"
    "flat out vec3 startPos;\n"
    "out vec3 vertPos;\n"
    "out vec4 vertColor;\n"
    "\n"
    "void main() {\n"
    "  vec4 pos    = mvp * vec4(position, 1.0);\n"
    "  gl_Position = pos;\n"
    "  vertPos     = pos.xyz / pos.w;\n"
    "  startPos    = vertPos;\n"
    "  vertColor   = color;\n"
    "}";
  const char *fogFragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec2 resolution;\n"
    "uniform uint stipple;\n"
    "uniform vec2 fog;\n"
    "uniform vec4 fogColor;\n"
    "flat in vec3 startPos;\n"
    "in vec3 vertPos;\n"
    "in vec4 vertColor;\n"
    "\n"
    "void main() {\n"
    "  vec2  dir  = (vertPos.xy - startPos.xy) * resolution / 2.0;\n"
    "  float dist = length(dir);\n"
    "  uint bit = uint(round(dist)) & 15U;\n"
    "  if ((stipple & (1U<<bit)) == 0U)\n"
    "    discard; \n"
    "  float f = clamp((vertPos.z * 0.5 - fog.x + 0.5) / (fog.y - fog.x), 0., 1.);\n"
    "  outputColor = vertColor * (1 - f) + fogColor * f;\n"
    "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec2 resolution;\n"
    "uniform uint stipple;\n"
    "flat in vec3 startPos;\n"
    "in vec3 vertPos;\n"
    "in vec3 vertColor;\n"
    "\n"
    "void main() {\n"
    "  vec2  dir  = (vertPos.xy - startPos.xy) * resolution / 2.0;\n"
    "  float dist = length(dir);\n"
    "  uint bit = uint(round(dist)) & 15U;\n"
    "  if ((stipple & (1U<<bit)) == 0U)\n"
    "    discard; \n"
    "  outputColor = vertColor;\n"
    "}";

  if (!tool_gl_createShader(id, vertex, withFog ? fogFragment : fragment, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    locs[i] = -1;
  locs[UNIFORM_MVP] = glGetUniformLocation(*id, "mvp");
  locs[UNIFORM_RESOLUTION] = glGetUniformLocation(*id, "resolution");
  locs[UNIFORM_STIPPLE] = glGetUniformLocation(*id, "stipple");
  if (withFog)
    {
      locs[UNIFORM_FOG] = glGetUniformLocation(*id, "fog");
      locs[UNIFORM_FOG_COLOR] = glGetUniformLocation(*id, "fogColor");      
    }

  g_debug("Tool GL: created coloured line shader %d (fog: %d).", *id, withFog);
  return TRUE;
}

/**
 * tool_gl_createTextShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @locs: (out) (array fixed-size=N_UNIFORMS): an array to store possible uniform ids.
 * @withFog: a boolean.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program rendering textures for text. On
 * success, the GL id for the program is stored in @id. It will be sensitive to fog
 * according to @withFog.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createTextShader(guint *id, gint locs[N_UNIFORMS], gboolean withFog, GError **error)
{
  const char *vertex =
    "#version 130\n"
    "in vec2 texPosition;\n"
    "in vec2 texCoord;\n"
    "uniform mat4 mvp;\n"
    "uniform mat4 mv;\n"
    "uniform vec3 offset;\n"
    "uniform vec2 resolution;\n"
    "uniform uvec2 texDimension;\n"
    "uniform vec3 position;\n"
    "uniform vec2 reduced;\n"
    "uniform vec2 align;\n"
    "out vec2 texFragCoord;\n"
    "out float z;\n"
    "\n"
    "void main() {\n"
    "  vec4 off = transpose(mv) * vec4(offset, 0.);\n"
    "  vec4 at = mvp * vec4(position + off.xyz, 1.);\n"
    "  at /= at.w;\n"
    "  z = at.z;\n"
    "  vec2 scale = vec2(2.,2.) * texDimension / resolution;\n"
    "  gl_Position = vec4(texPosition * scale, 0., 0.) + at + vec4(clamp(2. * reduced - scale * align - 1., vec2(-1., -1.), 1. - scale), 0., 0.);\n"
    "  texFragCoord = texCoord * texDimension;\n"
    "}";
  const char *fogFragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec4 color;\n"
    "uniform sampler2D textureData;\n"
    "uniform vec2 fog;\n"
    "uniform vec4 fogColor;\n"
    "in vec2 texFragCoord;\n"
    "in float z;\n"
    "\n"
    "void main() {\n"
    "  float f = clamp((z * 0.5 - fog.x + 0.5) / (fog.y - fog.x), 0., 1.);\n"
    "  vec4 x = texelFetch(textureData, ivec2(texFragCoord), 0);\n"
    "  float cMin = min(color.r, min(color.g, color.b));\n"
    "  float cMax = max(color.r, max(color.g, color.b));\n"
    "  if (0.5 * (cMin + cMax) < 0.33) {\n"
    "    outputColor = vec4(mix(vec3(1.,1.,1.), color.rgb, x.rgb), x.a * color.a);\n"
    "  } else {\n"
    "    outputColor = vec4(mix(vec3(0.,0.,0.), color.rgb, x.rgb), x.a * color.a);\n"
    "  }\n"
    "  outputColor = vec4(mix(outputColor.xyz, fogColor.xyz, f), outputColor.w);\n"
    "  if (outputColor.a == 0.) discard;\n"
    "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec4 color;\n"
    "uniform sampler2D textureData;\n"
    "in vec2 texFragCoord;\n"
    "\n"
    "void main() {\n"
    "  vec4 x = texelFetch(textureData, ivec2(texFragCoord), 0);\n"
    "  outputColor = vec4(mix(vec3(0.12,.61,0.19), color.rgb, x.rgb), x.a * color.a);\n"
    "  if (outputColor.a == 0.) discard;\n"
    "}";

  if (!tool_gl_createShader(id, vertex, withFog ? fogFragment : fragment, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    locs[i] = -1;
  locs[UNIFORM_MVP] = glGetUniformLocation(*id, "mvp");
  locs[UNIFORM_MV] = glGetUniformLocation(*id, "mv");
  locs[UNIFORM_OFFSET] = glGetUniformLocation(*id, "offset");
  locs[UNIFORM_COLOR] = glGetUniformLocation(*id, "color");
  locs[UNIFORM_RESOLUTION] = glGetUniformLocation(*id, "resolution");
  locs[UNIFORM_TEXT_DIM] = glGetUniformLocation(*id, "texDimension");
  locs[UNIFORM_TEXT_XYZ_POS] = glGetUniformLocation(*id, "position");
  locs[UNIFORM_TEXT_RED_POS] = glGetUniformLocation(*id, "reduced");
  locs[UNIFORM_TEXT_ALIGN] = glGetUniformLocation(*id, "align");

  if (withFog)
    {
      locs[UNIFORM_FOG] = glGetUniformLocation(*id, "fog");
      locs[UNIFORM_FOG_COLOR] = glGetUniformLocation(*id, "fogColor");      
    }

  g_debug("Tool GL: created text shader %d (fog: %d).", *id, withFog);
  return TRUE;
}

/**
 * tool_gl_createMaterialShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @locs: (out) (array fixed-size=N_UNIFORMS): an array to store possible uniform ids.
 * @withFog: a boolean.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program rendering surfaces with light effects. On
 * success, the GL id for the program is stored in @id. It will be sensitive to fog
 * according to @withFog.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createMaterialShader(guint *id, gint locs[N_UNIFORMS],
                                      gboolean withFog, GError **error)
{
  const char *vertex =
    "#version 130\n"
    "in vec3 position;\n"
    "in vec3 normal;\n"
    "uniform mat4 mvp;\n"
    "uniform mat4 mv;\n"
    "uniform vec3 translation;\n"
    "uniform vec3 light_position;\n"
    "uniform vec3 camera_position;\n"
    "uniform float ambient;\n"
    "uniform float diffuse;\n"
    "uniform float emission;\n"
    "out float vertex_z;\n"
    "out float vertex_material;\n"
    "out vec3 vertex_reflect;\n"
    "out vec3 vertex_camera;\n"
    "\n"
    "void main() {\n"
    "  gl_Position = mvp * vec4(position + translation, 1.0);\n"
    "  vertex_z = gl_Position.z / gl_Position.w;\n"
    "  vec3 vertex_light = normalize(light_position);\n"
    "  vec3 vertex_normal = normalize(mv * vec4(normal, 0.)).xyz;\n"
    "  float diff = max(dot(vertex_normal, vertex_light), 0.0);\n"
    "  vertex_material = ambient + emission + diff * diffuse;\n"
    "  vertex_reflect = reflect(-vertex_light, vertex_normal);\n"
    "  vertex_camera = normalize((mv * vec4(camera_position - position, 0.)).xyz);\n"
    "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec4 color;\n"
    "uniform float specular;\n"
    "uniform float shininess;\n"
    "in float vertex_material;\n"
    "in vec3 vertex_reflect;\n"
    "in vec3 vertex_camera;\n"
    "\n"
    "void main() {\n"
    "  float spec = pow(max(dot(vertex_camera, vertex_reflect), 0.0), max(1e-4, shininess));\n"
    "  float f = vertex_material + spec * specular;\n"
    "  outputColor = vec4(f * color.x, f * color.y, f * color.z, color.w);\n"
    "}";
  const char *fogFragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform vec4 color;\n"
    "uniform float specular;\n"
    "uniform float shininess;\n"
    "uniform vec2 fog;\n"
    "uniform vec4 fogColor;\n"
    "in float vertex_z;\n"
    "in float vertex_material;\n"
    "in vec3 vertex_reflect;\n"
    "in vec3 vertex_camera;\n"
    "\n"
    "void main() {\n"
    "  float spec = pow(max(dot(vertex_camera, vertex_reflect), 0.0), max(1e-4, shininess));\n"
    "  float a = vertex_material + spec * specular;\n"
    "  float f = clamp((vertex_z * 0.5 - fog.x + 0.5) / (fog.y - fog.x), 0., 1.);\n"
    "  outputColor = vec4(a * color.r, a * color.g, a * color.b, color.w) * (1 - f) + fogColor * f;\n"
    "}";

  if (!tool_gl_createShader(id, vertex,  withFog ? fogFragment : fragment, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    locs[i] = -1;
  locs[UNIFORM_MVP] = glGetUniformLocation(*id, "mvp");
  locs[UNIFORM_MV] = glGetUniformLocation(*id, "mv");
  locs[UNIFORM_TRANSLATION] = glGetUniformLocation(*id, "translation");
  locs[UNIFORM_COLOR] = glGetUniformLocation(*id, "color");
  locs[UNIFORM_AMBIENT] = glGetUniformLocation(*id, "ambient");
  locs[UNIFORM_DIFFUSE] = glGetUniformLocation(*id, "diffuse");
  locs[UNIFORM_SPECULAR] = glGetUniformLocation(*id, "specular");
  locs[UNIFORM_SHININESS] = glGetUniformLocation(*id, "shininess");
  locs[UNIFORM_EMISSION] = glGetUniformLocation(*id, "emission");
  locs[UNIFORM_LIGHT_POS] = glGetUniformLocation(*id, "light_position");
  locs[UNIFORM_CAMERA_POS] = glGetUniformLocation(*id, "camera_position");

  if (withFog)
    {
      locs[UNIFORM_FOG] = glGetUniformLocation(*id, "fog");
      locs[UNIFORM_FOG_COLOR] = glGetUniformLocation(*id, "fogColor");      
    }

  g_debug("Tool GL: created material shader %d.", *id);
  return TRUE;
}

/**
 * tool_gl_createOrthoShader:
 * @id: (out caller-allocates): a location for a GL id.
 * @locs: (out) (array fixed-size=N_UNIFORMS): an array to store possible uniform ids.
 * @error: (allow-none) (out caller-allocates): an error location.
 *
 * Create a GLSL program rendering per vertex colours in the window coordinates. On
 * success, the GL id for the program is stored in @id.
 *
 * Since: 3.9
 *
 * Returns: TRUE on success.
 **/
gboolean tool_gl_createOrthoShader(guint *id, gint locs[N_UNIFORMS], GError **error)
{
  const char *vertex =
    "#version 130\n"
    "in vec3 position;\n"
    "in vec4 color;\n"
    "uniform vec2 resolution;\n"
    "out vec4 vertColor;\n"
    "\n"
    "void main() {\n"
    "  gl_Position = vec4(2. * position.x / resolution.x - 1.f, 2. * position.y / resolution.y - 1.f, -2./100., 1.);\n"
    "  vertColor = color;\n"
    "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "in vec4 vertColor;\n"
    "\n"
    "void main() {\n"
    "  outputColor = vertColor;\n"
    "}";

  if (!tool_gl_createShader(id, vertex, fragment, error))
    return FALSE;

  for (guint i = 0; i < N_UNIFORMS; i++)
    locs[i] = -1;
  locs[UNIFORM_RESOLUTION] = glGetUniformLocation(*id, "resolution");

  g_debug("Tool GL: created ortho shader %d.", *id);
  return TRUE;
}
