/* 
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* copyright Schrodinger, LLC.
D* -------------------------------------------------------------------
E* It is unlawful to modify or remove this copyright notice.
F* -------------------------------------------------------------------
G* Please see the accompanying LICENSE file for further information. 
H* -------------------------------------------------------------------
I* Additional authors of this source file include:
-*
-* 
-*
Z* -------------------------------------------------------------------
*/

#include "os_gl.h"
#include "os_python.h"
#include <string.h>
#include <iostream>
#include "ShaderMgr.h"
#include "ListMacros.h"
#include "PyMOLOptions.h"
#include "Feedback.h"
#include "MemoryDebug.h"
#include "Setting.h"
#include "Scene.h"
#include "Color.h"
#include "Vector.h"
#include "Util.h"
#include "Util2.h"
#include "Texture.h"
#include "FileStream.h"
#include "Matrix.h"
#include "Parse.h"

#ifndef _PYMOL_NO_AA_SHADERS
#endif

#include "CGO.h"
#ifdef _WEBGL
#include "Matrix.h"
#include "WebPyMOLLibrary.h"
#endif
#define MAX_LOG_LEN 1024

#include <algorithm>
#include <sstream>
#include <stack>
#include <vector>
#include <functional>
/*  Texture Usage:

    0 - for not-PURE_OPENGL_ES_2: ObjectVolume: volumeTex
        for WEBGL: SCHRODINGER logo
    1 - for not-PURE_OPENGL_ES_2: Volume: either colorTex1D or colorTex2D
        for _PYMOL_PRECOMPUTED_LIGHTING: Lighting Texture (ShaderMgr->lightingTexture)
    2 - FXAA - color_texture
        SMAA1 - colorTex
        SMAA3 - colorTex
    3 - SMAA3 - blendTex
        Label/Indicator Shader : textureMap (both PURE_OPENGL_ES_2 and non-PURE_OPENGL_ES_2
    4 - Background Texture: bgTextureMap
    5 - OIT - 2nd pass : accumTex
        Volumes - carvemask
    6 - OIT - 2nd pass : revealageTex
        SMAA2 - edgesTex
    7 - SMAA2 - areaTex
        OIT Copy - colorTex
    8 - SMAA2 - searchTex

 */

#ifndef _DEAD_CODE_DIE
#define SUPPRESS_GEOMETRY_SHADER_ERRORS
#define SUPPRESS_TESSELLATION_SHADER_ERRORS
#endif

#define CONNECTOR_GS_NUM_VERTICES  31

#define SCENEGETIMAGESIZE SceneGetWidthHeight

#include "ShaderText.h"

#define WARNING_IF_GLERROR(msg) {		\
  GLenum err; \
  if ((err = glGetError())){ \
    PRINTFB(G, FB_ShaderMgr, FB_Warnings) "GLERROR 0x%04x: %s\n", err, msg ENDFB(G); \
  } \
}

static void glShaderSource1String(GLuint shad, const std::string &strobj){
  const GLchar *str = (const GLchar *)strobj.c_str();
  glShaderSource(shad, 1, (const GLchar **)&str, nullptr);
}

bool CShaderPrg::reload(){
  // skip programs with empty file names, assume their code is managed
  // outside of the reload logic (like ARB shaders).
  if (is_valid || vertfile.empty())
    return true;

  std::string gs, vs, fs, ts_ctrl, ts_eval;
  CShaderMgr *I = G->ShaderMgr;
  GLint status;

  if (geomParams) {
    gs = I->GetShaderSource(geomParams->file);
  }

  if (tessParams) {
    ts_ctrl = I->GetShaderSource(tessParams->controlFile);
    ts_eval = I->GetShaderSource(tessParams->evaluationFile);
  }

  vs = I->GetShaderSource(vertfile);
  fs = I->GetShaderSource(fragfile);

  WARNING_IF_GLERROR("CShaderPrg::reload begin");

  PRINTFB(G, FB_ShaderMgr, FB_Blather)
    "Loading shader named: %s\n", name.c_str()
    ENDFB(G);

  if (!id) {
    id = glCreateProgram();
  }

#ifndef PURE_OPENGL_ES_2
  if (geomParams && SettingGet<bool>(G, cSetting_use_geometry_shaders)) {
    if (!geomParams->id) {
      geomParams->id = glCreateShader(GL_GEOMETRY_SHADER);

      GLenum err;
      if ((err=glGetError()) || !geomParams->id) {
        PRINTFB(G, FB_ShaderMgr, FB_Errors)
          " Error: geometry shader creation failed. name=%s err=0x%x\n", name.c_str(), err ENDFB(G);
        return false;
      }

      glAttachShader(id, geomParams->id);
    }

    glShaderSource1String(geomParams->id, gs);
    glCompileShader((GLuint) geomParams->id);
    glGetShaderiv(geomParams->id, GL_COMPILE_STATUS, &status);

    if (!status) {
#ifndef SUPPRESS_GEOMETRY_SHADER_ERRORS
      ErrorMsgWithShaderInfoLog(geomParams->id, "geometry shader compilation failed.");
#endif
      glDetachShader(id, geomParams->id);
      glDeleteShader(geomParams->id);
      geomParams->id = 0;
      return false;
    }

    glProgramParameteriEXT(id, GL_GEOMETRY_INPUT_TYPE_EXT, geomParams->input);
    glProgramParameteriEXT(id, GL_GEOMETRY_OUTPUT_TYPE_EXT, geomParams->output);
    glProgramParameteriEXT(id, GL_GEOMETRY_VERTICES_OUT_EXT, geomParams->numVertsOut);

    PRINTFB(G, FB_ShaderMgr, FB_Debugging)
      " ShaderPrg-Debug: geometry shader compiled.\n" ENDFB(G);
  } else if (geomParams && geomParams->id) {
    // for manually switching off geometry shaders (set use_geometry_shaders, off)
    glDetachShader(id, geomParams->id);
    glDeleteShader(geomParams->id);
    geomParams->id = 0;
  }

  WARNING_IF_GLERROR("CShaderPrg::reload after geometry shader");

  if (tessParams && SettingGet<bool>(G, cSetting_use_tessellation_shaders)) {
    if (!tessParams->controlID) {
      tessParams->controlID = glCreateShader(GL_TESS_CONTROL_SHADER);
      GLenum err;
      if ((err = glGetError()) || !tessParams->controlID) {
        PRINTFB(G, FB_ShaderMgr, FB_Errors)
        " Error: geometry shader creation failed. name=%s err=0x%x\n",
            name.c_str(), err ENDFB(G);
        return false;
      }
      glAttachShader(id, tessParams->controlID);
    }
    glShaderSource1String(tessParams->controlID, ts_ctrl);
    glCompileShader((GLuint) tessParams->controlID);
    glGetShaderiv(tessParams->controlID, GL_COMPILE_STATUS, &status);

    if (!status) {
      ErrorMsgWithShaderInfoLog(
          tessParams->controlID, "tess ctrl shader compilation failed.");
      glDetachShader(id, tessParams->controlID);
      glDeleteShader(tessParams->controlID);
      tessParams->controlID = 0;
      return false;
    }

    if (!tessParams->evaluationID) {
      tessParams->evaluationID = glCreateShader(GL_TESS_EVALUATION_SHADER);
      GLenum err;
      if ((err = glGetError()) || !tessParams->evaluationID) {
        PRINTFB(G, FB_ShaderMgr, FB_Errors)
        " Error: tess eval shader creation failed. name=%s err=0x%x\n",
            name.c_str(), err ENDFB(G);
        return false;
      }
      glAttachShader(id, tessParams->evaluationID);
    }
    glShaderSource1String(tessParams->evaluationID, ts_eval);
    glCompileShader((GLuint) tessParams->evaluationID);
    glGetShaderiv(tessParams->evaluationID, GL_COMPILE_STATUS, &status);

    if (!status) {
      ErrorMsgWithShaderInfoLog(
          tessParams->evaluationID, "tess ctrl evaluation compilation failed.");
      glDetachShader(id, tessParams->evaluationID);
      glDeleteShader(tessParams->evaluationID);
      tessParams->evaluationID = 0;
      return false;
    }
  }
#endif // PURE_OPENGL_ES2

  // vertex shader
  {
    if (!vid) {
      vid = glCreateShader(GL_VERTEX_SHADER);
      glAttachShader(id, vid);
    }

    glShaderSource1String(vid, vs);
    glCompileShader((GLuint) vid);
    glGetShaderiv(vid, GL_COMPILE_STATUS, &status);

    if (!status) {
      ErrorMsgWithShaderInfoLog(vid, "vertex shader compilation failed.");
      return false;
    }
  }

  // fragment shader
  {
    if (!fid) {
      fid = glCreateShader(GL_FRAGMENT_SHADER);
      glAttachShader(id, fid);
    }

    glShaderSource1String(fid, fs);
    glCompileShader((GLuint) fid);
    glGetShaderiv(fid, GL_COMPILE_STATUS, &status);

    if (!status) {
      ErrorMsgWithShaderInfoLog(fid, "fragment shader compilation failed.");
      return false;
    }
  }

  uniforms.clear();
  uniform_set = 0;

  // it is valid to bind unused names, and to bind multiple names to the same index
  if (!name.compare(0, 8, "cylinder")){
    glBindAttribLocation(id, CYLINDER_VERTEX1, "attr_vertex1");
    glBindAttribLocation(id, CYLINDER_VERTEX2, "attr_vertex2");
    glBindAttribLocation(id, CYLINDER_COLOR, "a_Color");
    glBindAttribLocation(id, CYLINDER_COLOR2, "a_Color2");
    glBindAttribLocation(id, CYLINDER_RADIUS, "attr_radius");
    glBindAttribLocation(id, CYLINDER_CAP, "a_cap");
  } else {
    glBindAttribLocation(id, VERTEX_POS, "a_Vertex");
    glBindAttribLocation(id, VERTEX_COLOR, "a_Color");
    glBindAttribLocation(id, VERTEX_NORMAL, "a_Normal");
    glBindAttribLocation(id, 0, "attr_worldpos");
  }
  WARNING_IF_GLERROR("after glBindAttribLocation");

  is_linked = false;
  is_valid = true;

  return true;
}

#define MASK_SHADERS_PRESENT_GEOMETRY 0x2;
#define MASK_SHADERS_PRESENT_SMAA 0x4;
#define MASK_SHADERS_PRESENT_TESSELLATION 0x8;

static void getGLVersion(PyMOLGlobals * G, int *major, int* minor);
static void getGLSLVersion(PyMOLGlobals * G, int* major, int* minor);

static void disableShaders(PyMOLGlobals * G);

/**
 * Use this to turn off shaders if the renderer cannot use them.
 */
void disableShaders(PyMOLGlobals * G) {
    /* Auto-disable shader-based rendering */
    SettingSetGlobal_b(G, cSetting_use_shaders, false);
}

static void disableGeometryShaders(PyMOLGlobals * G) {
  SettingSetGlobal_b(G, cSetting_use_geometry_shaders, false);
  if(G->ShaderMgr)
    G->ShaderMgr->SetPreprocVar("use_geometry_shaders", false);

  if (G->Option && !G->Option->quiet)
    PRINTFB(G, FB_ShaderMgr, FB_Warnings)
      " Geometry shaders not available\n" ENDFB(G);
}

static void disableTessellationShaders(PyMOLGlobals* G)
{
  SettingSet<bool>(G, cSetting_use_tessellation_shaders, false);
  if (G->ShaderMgr) {
    G->ShaderMgr->SetPreprocVar("use_tessellation_shaders", false);
  }

  if (G->Option && !G->Option->quiet) {
    PRINTFB(G, FB_ShaderMgr, FB_Warnings)
    " Tessellation shaders not available\n" ENDFB(G);
  }
}

/**
 * Replace strings from a list of pairs.
 *
 * src: string to modify
 * replaceStrings: map of strings to replace (as consecutive elements in an
 *                 array like {from1, to1, from2, to2, ..., ""}
 * returns: new string
 */
static std::string stringReplaceAll(const std::string &src, const std::string * replaceStrings) {
  std::string dest = src;
  for (int i = 0; !replaceStrings[i].empty(); i += 2) {
    int slen1 = replaceStrings[i].length();
    int slen2 = replaceStrings[i + 1].length();
    for (size_t pl = 0;
        (pl = dest.find(replaceStrings[i], pl)) != std::string::npos;
        pl += slen2) {
      dest.replace(pl, slen1, replaceStrings[i + 1]);
    }
  }
  return dest;
}

/**
 * Reload "CallComputeColorForLight" shader replacement string
 */
void CShaderMgr::Reload_CallComputeColorForLight(){
  if ((reload_bits & RELOAD_CALLCOMPUTELIGHTING)) {
    reload_bits &= ~RELOAD_CALLCOMPUTELIGHTING;
  } else {
    return;
  }

  if (SettingGetGlobal_b(G, cSetting_precomputed_lighting)) {
    Generate_LightingTexture();
    return;
  }

  int light_count = SettingGetGlobal_i(G, cSetting_light_count);
  int spec_count = SettingGetGlobal_i(G, cSetting_spec_count);
  std::ostringstream accstr;

  std::string rawtemplate = GetShaderSource("call_compute_color_for_light.fs");

  std::string lightstrings[] = {
    "`light`", "0",
    "`postfix`", "_0",
    ""
  };

  accstr << stringReplaceAll(rawtemplate, lightstrings);

  if (light_count > 8){
    PRINTFB(G, FB_ShaderMgr, FB_Details)
      " ShaderMgr-Detail: using 8 lights (use precomputed_lighting for light_count > 8)\n"
      ENDFB(G);
    light_count = 8;
  }

  // no postfix for 1..light_count
  lightstrings[3] = "";

  for (int i=1; i<light_count; i++){
    std::ostringstream lstr;
    lstr << i;
    lightstrings[1] = lstr.str(); // std::to_string(i)

    if (i == spec_count + 1) {
      // no specular for [spec_count + 1 .. light_count]
      lightstrings[3] = " * 0.0";
    }

    accstr << stringReplaceAll(rawtemplate, lightstrings);
  }

  SetShaderSource("CallComputeColorForLight", accstr.str());
}

void CShaderMgr::Invalidate_All_Shaders(){
  for (auto& prog : programs) {
    prog.second->Invalidate();
  }
}

void CShaderMgr::Reload_All_Shaders(){
  Reload_Shader_Variables();
  Reload_CallComputeColorForLight();

  if (SettingGetGlobal_i(G, cSetting_transparency_mode) == 3) {
    Reload_Derivatives("NO_ORDER_TRANSP");
  }

  for (auto& prog : programs) {
    if (prog.second->derivative.empty())
      prog.second->reload();
  }
}

static bool shader_cache_initialized = false;

// filename -> contents (static filesystem)
static std::map<std::string, const char *> shader_cache_raw;

// preproc variable -> nullptr terminated list of filenames ("used by")
std::map<std::string, const char **> ifdef_deps;

// filename -> nullptr terminated list of filenames ("included by")
std::map<std::string, const char **> include_deps;

std::string CShaderMgr::GetShaderSource(const std::string &filename)
{
  return m_shaderPreprocessor.getSource(filename);
}

std::map<std::string, const char*>* CShaderMgr::GetRawShaderCache()
{
  return &shader_cache_raw;
}

#define FREE_AND_REPLACE_WITH(var, with) if (var) free(var);  var = with;

void CShaderMgr::Reload_Shader_Variables() {
  if ((reload_bits & RELOAD_VARIABLES)) {
    reload_bits &= ~RELOAD_VARIABLES;
  } else {
    return;
  }

  int bg_image_mode = SettingGetGlobal_i(G, cSetting_bg_image_mode);  
  int bg_gradient = SettingGetGlobal_b(G, cSetting_bg_gradient);  
  int bg_image_mode_solid;
  int stereo, stereo_mode;
  const char * bg_image_filename = SettingGet_s(G, nullptr, nullptr, cSetting_bg_image_filename);
  short bg_image = bg_image_filename && bg_image_filename[0];
  bg_image_mode_solid = !(bg_gradient || bg_image || OrthoBackgroundDataIsSet(*G->Ortho));

  SetPreprocVar("bg_image_mode_solid", bg_image_mode_solid);
  if (!bg_image_mode_solid) {
    SetPreprocVar("bg_image_mode_1_or_3", (bg_image_mode == 1 || bg_image_mode == 3));
    SetPreprocVar("bg_image_mode_2_or_3", (bg_image_mode == 2 || bg_image_mode == 3));
  }

#ifdef _PYMOL_IP_EXTRAS
  SetPreprocVar("volume_mode", SettingGetGlobal_i(G, cSetting_volume_mode));
#endif

  SetPreprocVar("ortho", SettingGetGlobal_i(G, cSetting_ortho));
  SetPreprocVar("depth_cue", SettingGetGlobal_b(G, cSetting_depth_cue)
      && SettingGetGlobal_b(G, cSetting_fog) != 0.0F);

#ifndef PURE_OPENGL_ES_2
  SetPreprocVar("use_geometry_shaders", SettingGetGlobal_b(G, cSetting_use_geometry_shaders));
#endif

  SetPreprocVar("line_smooth", SettingGetGlobal_b(G, cSetting_line_smooth));

  stereo = SettingGetGlobal_i(G, cSetting_stereo);
  stereo_mode = SettingGetGlobal_i(G, cSetting_stereo_mode);

#ifdef _PYMOL_OPENVR
  SetPreprocVar("openvr_enabled", stereo && stereo_mode == cStereo_openvr);
#endif

  SetPreprocVar("ANAGLYPH", stereo && stereo_mode == cStereo_anaglyph);
  SetPreprocVar("ray_trace_mode_3", SettingGetGlobal_i(G, cSetting_ray_trace_mode) == 3);
  SetPreprocVar("transparency_mode_3", SettingGetGlobal_i(G, cSetting_transparency_mode)==3);

#ifndef _PYMOL_NO_AA_SHADERS
#endif

  SetPreprocVar("precomputed_lighting", SettingGetGlobal_b(G, cSetting_precomputed_lighting));
  SetPreprocVar("ray_transparency_oblique", SettingGetGlobal_f(G, cSetting_ray_transparency_oblique) > R_SMALL4);

  int chromadepth = SettingGetGlobal_i(G, cSetting_chromadepth);
  SetPreprocVar("chromadepth", chromadepth != 0);
  SetPreprocVar("chromadepth_postlighting", chromadepth == 2);
}

/* ============================================================================
 * ShaderMgrInit is called from PyMOL.c during start up; it just allocates
 * the global ShaderMgr
 */
bool ShaderMgrInit(PyMOLGlobals * G) {
  // initialize some globals (do this only once)
  if (!shader_cache_initialized) {
    shader_cache_initialized = true;

    // make #include dependency map from flat array
    for (const char ** ptr = _include_deps; *ptr; ++ptr) {
      include_deps[ptr[0]] = ptr + 1;
      while (*(++ptr)) {}
    }

    // make #ifdef dependency map from flat array
    for (const char ** ptr = _ifdef_deps; *ptr; ++ptr) {
      ifdef_deps[ptr[0]] = ptr + 1;
      while (*(++ptr)) {}
    }

    // make shader file cache from flat array
    for (const char ** ptr = _shader_cache_raw; *ptr; ptr += 2) {
      shader_cache_raw[ptr[0]] = *(ptr + 1);
    }
  }

  G->ShaderMgr = new CShaderMgr(G);

  if(!G->ShaderMgr)
    return false;

  return true;
}

/**
 * Print the given message as ShaderMgr-Error, followed by the shader info log.
 */
void CShaderPrg::ErrorMsgWithShaderInfoLog(const GLuint sid, const char * msg) {
  if (!G->Option || G->Option->quiet)
    return;

  GLint infoLogLength = 0;
  glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &infoLogLength);
  std::vector<GLchar> infoLog(infoLogLength);
  glGetShaderInfoLog(sid, infoLogLength, nullptr, infoLog.data());

  PRINTFB(G, FB_ShaderPrg, FB_Errors) " ShaderPrg-Error: %s; name='%s'\n",
    msg, name.c_str() ENDFB(G);

  PRINTFB(G, FB_ShaderPrg, FB_Errors) " ShaderPrg-Error-InfoLog:\n%s\n",
    infoLog.data() ENDFB(G);
}

/* ShaderMgrConfig -- Called from PyMOL.c, configures the global ShaderMgr
 * This needs to be called once the OpenGL context has been created, it is 
 * called from MainInit() for PyMol, and from PyMOL_ConfigureShadersGL() for
 * other programs (i.e., JyMOL, AxPyMOL, etc.).
 */
void CShaderMgr::Config() {
  if (!G || !G->HaveGUI) /* && G->ValidContext); */
    return;

  glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, line_width_range);

#ifndef PURE_OPENGL_ES_2
  GLenum err = glewInit();

#ifdef GLEW_ERROR_NO_GLX_DISPLAY
  if (err == GLEW_ERROR_NO_GLX_DISPLAY) {
    err = GLEW_OK;
  }
#endif

  if (GLEW_OK==err) {
    GLint gl_major = 0, gl_minor = 0;
    getGLVersion(G, &gl_major, &gl_minor);

    PRINTFB(G, FB_ShaderMgr, FB_Details)
      " Detected OpenGL version %d.%d.", gl_major, gl_minor ENDFB(G);

    if (GLEW_VERSION_2_0) {
      G->Feedback->add(" Shaders available.\n");
    }
    else { 
      G->Feedback->add(" Shaders and volumes unavailable.\n");
      disableShaders(G);
      return;
    }
  } 
  else {
    /* print info on glew error? */
    G->Feedback->add(" There was an error intializing GLEW.  Basic graphics, including\n shaders and volumes may be unavailable.\n");
    disableShaders(G);
    fprintf(stderr, " GLEW-Error: %s\n", glewGetErrorString(err));
    return;
  }
#endif

  // static preprocessor values
  m_shaderPreprocessor.setVar("GLEW_VERSION_3_0", GLEW_VERSION_3_0 ? true : false);
  if (TM3_IS_ONEBUF){
    m_shaderPreprocessor.setVar("ONE_DRAW_BUFFER", true);
  }
#ifdef PURE_OPENGL_ES_2
  m_shaderPreprocessor.setVar("PURE_OPENGL_ES_2", true);
  m_shaderPreprocessor.setVar("PYMOL_WEBGL", true);
  m_shaderPreprocessor.setVar("PYMOL_WEBGL_IOS", true);
#else
  m_shaderPreprocessor.setVar("gl_VertexID_enabled", GLEW_EXT_gpu_shader4);
#endif

  // shaders
#define make_program(name, ...) programs[name] = new CShaderPrg(G, name, __VA_ARGS__)

  make_program("bg", "bg.vs", "bg.fs");
  make_program("indicator", "indicator.vs", "indicator.fs");
  make_program("label", "label.vs", "label.fs");

#ifndef PURE_OPENGL_ES_2
  make_program("volume", "volume.vs", "volume.fs");
#endif

  make_program("default", "default.vs", "default.fs");
  make_program("surface", "surface.vs", "surface.fs");

  make_program("line", "line.vs", "line.fs");

  make_program("screen", "screen.vs", "screen.fs");

  if (GLEW_EXT_geometry_shader4 && GLEW_EXT_gpu_shader4){
    auto geoParams = pymol::make_copyable<CShaderPrg::GeometryShaderParams>(
        CShaderPrg::GeometryShaderParams{"connector.gs", GL_POINTS,
            GL_TRIANGLE_STRIP, CONNECTOR_GS_NUM_VERTICES});
    make_program(
        "connector", "connector.vs", "connector.fs", std::move(geoParams));
  } else {
    make_program("connector", "connector.vs", "connector.fs");
  }

  if (GLEW_ARB_tessellation_shader && GLEW_ARB_gpu_shader5) {
    auto tessParams =
        pymol::make_copyable<CShaderPrg::TessellationShaderParams>(
            CShaderPrg::TessellationShaderParams{"bezier.tsc", "bezier.tse"});
    make_program(
        "bezier", "bezier.vs", "bezier.fs", nullptr, std::move(tessParams));
  }

  if (GET_FRAGDEPTH_SUPPORT()) {
    make_program("cylinder", "cylinder.vs", "cylinder.fs");
    make_program("sphere", "sphere.vs", "sphere.fs");
  }  

  make_program("ramp", "ramp.vs", "ramp.fs");
  programs["ramp"]->uniformLocations[RAMP_OFFSETPT] = "offsetPt";

  make_program("oit", "oit.vs", "oit.fs");
  make_program("copy", "copy.vs", "copy.fs");
  make_program("trilines", "trilines.vs", "trilines.fs");

  Reload_Shader_Variables();
  Reload_CallComputeColorForLight();

  // shaders availability test
  ok_assert(1, programs["default"]->reload());

#ifndef PURE_OPENGL_ES_2
  // geometry shaders availability test
  if (const auto geomIt = programs.find("connector");
      geomIt != programs.end()) {
    const auto& geom = geomIt->second;
    if (geom->reload()) {
      const auto& geomParams = geom->geomParams;
      if (geomParams && geomParams->id) {
        shaders_present |= MASK_SHADERS_PRESENT_GEOMETRY;
      }
    }
  } else {
    disableGeometryShaders(G);
  }

  // tessellation shaders availability test
  if (programs.find("bezier") != programs.end()) {
    auto& tess = programs["bezier"];
    if (tess->reload()) {
      auto& tessParams = tess->tessParams;
      if (tessParams && tessParams->controlID && tessParams->evaluationID) {
        shaders_present |= MASK_SHADERS_PRESENT_TESSELLATION;
      }
    }
  } else {
    disableTessellationShaders(G);
  }
#else
  SettingSetGlobal_b(G, cSetting_use_geometry_shaders, 0);
  SettingSet<bool>(G, cSetting_use_tessellation_shaders, 0);
#endif

#define check_program(name, setting, value) {   \
    if (!programs[name]->reload()) {            \
      SettingSetGlobal_i(G, setting, value);    \
      programs.erase(name);                     \
    }}                                          \
  // other shader compilation tests
  if (GET_FRAGDEPTH_SUPPORT()) {
  check_program("cylinder", cSetting_render_as_cylinders, 0);
  check_program("sphere", cSetting_sphere_mode, 0);
  }

#ifndef _PYMOL_NO_AA_SHADERS
#endif

  // get filename -> shader program dependencies
  for (auto& prog : programs) {
    RegisterDependantFileNames(prog.second);
  }

  // make transparency_mode_3 shader derivatives
  MakeDerivatives("_t", "NO_ORDER_TRANSP");

#ifndef PURE_OPENGL_ES_2
  /* report GLSL version */
  if (G && G->Option && !G->Option->quiet) {
    char buf[50];
    int major, minor;
    getGLSLVersion(G, &major, &minor);
    sprintf(buf, " Detected GLSL version %d.%d.\n", major, minor);
    G->Feedback->add(buf);
  }
#endif
  shaders_present |= 0x1;
  SettingSetGlobal_b(G, cSetting_use_shaders, true);

  is_configured = true;
  return;
ok_except1:
  disableShaders(G);
  G->ShaderMgr->shaders_present = 0;
  is_configured = true;
}

/* getGLVersion -- determine user's GL version
 * PARAMS
 * major, return value for major
 * minor, return value for minor
 *
 * RETURNS
 * nothing; writes to major and minor
 */
void getGLVersion(PyMOLGlobals * G, int *major, int* minor) {
  /* query the version string */
  const char* verstr = (const char*) glGetString(GL_VERSION);
  /* attempt to store the values into major and minor */
  if (!verstr || sscanf(verstr, "%d.%d", major, minor) != 2) {
    *major = *minor = 0;
    /* Use PyMOL FB system, instead of fprintf */
    PRINTFD(G, FB_ObjectVolume) 
      "Invalid GL_VERSION format.\n" ENDFD;
  }
}


/* getGLSLVersion -- determine user's GLSL version
 * PARAMS
 * major, rval for major
 * minor, rval for minor
 */
#ifndef PURE_OPENGL_ES_2
void getGLSLVersion(PyMOLGlobals * G, int* major, int* minor) {
  int gl_major, gl_minor;
  *major = *minor = 0;

  /* grab the GL version */
  getGLVersion(G, &gl_major, &gl_minor);

  /* GL version 1 */
  if (1==gl_major) {
    const char* extstr = (const char*) glGetString(GL_EXTENSIONS);
    if (extstr && strstr(extstr, "GL_ARB_shading_language_100")) {
      *major = 1;
      *minor = 0;
    }
  }
  /* GL > version 1 */
  else if (gl_major>=2) {
    const char* verstr = (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION);

    if (!verstr || sscanf(verstr, "%d.%d", major, minor) != 2) {
      *major = *minor = 0;

      if (G && G->Option && !G->Option->quiet) {
        PRINTFD(G, FB_ObjectVolume) 
          "Invalid GL_SHADING_LANGUAGE_VERSION format.\n" ENDFD;
      }
    }
  }
}
#endif



/* ============================================================================
 * CShaderMgr -- Simple Shader Manager class
 */
CShaderMgr::CShaderMgr(PyMOLGlobals * G_)
  : m_shaderPreprocessor(G_, CShaderMgr::GetRawShaderCache())
{
  G = G_;
  current_shader = nullptr;
  shaders_present = 0;
  stereo_flag = 0;
  stereo_blend = 0;
#ifdef _PYMOL_LIB
  print_warnings = 0;
#else
  print_warnings = 1;
#endif

  lightingTexture = 0;
  is_picking = 0;
  reload_bits = RELOAD_ALL_SHADERS;

#ifndef _WEBGL
  vbos_to_free.reserve(256);
#endif
}

CShaderMgr::~CShaderMgr() {
  for (auto& prog : programs) {
    delete prog.second;
  }
  programs.clear();

  freeGPUBuffer(offscreen_rt);
  freeGPUBuffer(offscreen_ortho_rt);
  freeGPUBuffer(offscreen_sized_image_rt);

  FreeAllVBOs();
}

int CShaderMgr::AddShaderPrg(CShaderPrg * s) {
  if (!s)
    return 0;
  const std::string& name = s->name;
  if (programs.find(name)!=programs.end()){
    delete programs[name];
  }
  programs[name] = s;
  return 1;
}

int CShaderMgr::RemoveShaderPrg(const std::string& name) {
  if (programs.find(name) != programs.end()){
    delete programs[name];
  }
  return 1;
}

/**
 * Lookup a shader program by name and set it as the `current_shader` of the
 * shader manager. If `pass` is provided and is less than zero, and we are
 * in transparency_mode 3, then look up the NO_ORDER_TRANSP derivative.h
 */
CShaderPrg * CShaderMgr::GetShaderPrg(std::string name, short set_current_shader, RenderPass pass) {
  if (pass == RenderPass::Transparent && SettingGetGlobal_i(G, cSetting_transparency_mode) == 3) {
    name += "_t";
  }

  auto it = programs.find(name);
  if (it == programs.end())
    return nullptr;

  if (set_current_shader)
    current_shader = it->second;

  return it->second;
}

int CShaderMgr::ShaderPrgExists(const char * name){
  return (programs.find(name) != programs.end());
}

int CShaderMgr::ShadersPresent() {
  return shaders_present;
}

int CShaderMgr::GeometryShadersPresent() {
  return shaders_present & MASK_SHADERS_PRESENT_GEOMETRY;
}

/**
 * glDeleteBuffers for vbos_to_free
 */
void CShaderMgr::FreeAllVBOs() {
#ifndef _WEBGL
  freeAllGPUBuffers();

  LOCK_GUARD_MUTEX(lock, vbos_to_free_mutex);

  if (vbos_to_free.empty())
    return;

  glDeleteBuffers(vbos_to_free.size(), &vbos_to_free[0]);

  vbos_to_free.clear();
#endif
}

void CShaderMgr::AddVBOsToFree(GLuint *vboid, int nvbos){
  int i;
  for (i=0; i<nvbos; i++){
    if (vboid[i]>0)
        AddVBOToFree(vboid[i]);
  }
}

/**
 * thread-safe deferred glDeleteBuffers(1, &vboid)
 */
void CShaderMgr::AddVBOToFree(GLuint vboid){
#ifdef _WEBGL // No threads, immediately delete
  if (glIsBuffer(vboid)) {
    glDeleteBuffers(1, &vboid);
  } else {
    PRINTFB(G, FB_ShaderMgr, FB_Warnings) "WARNING: CShaderMgr_AddVBOToFree() buffer is not a VBO %d", vboid ENDFB(G);
  }
#else
  LOCK_GUARD_MUTEX(lock, vbos_to_free_mutex);
  vbos_to_free.push_back(vboid);
#endif
}

CShaderPrg *CShaderMgr::Enable_DefaultShaderWithSettings(
    const CSetting *set1,
    const CSetting *set2, RenderPass pass) {
  CShaderPrg * shaderPrg = Get_DefaultShader(pass);
  return Setup_DefaultShader(shaderPrg, set1, set2);
}

CShaderPrg *CShaderMgr::Enable_DefaultShader(RenderPass pass){
  CShaderPrg * shaderPrg = Get_DefaultShader(pass);
  return Setup_DefaultShader(shaderPrg, nullptr, nullptr);
}

CShaderPrg *CShaderMgr::Enable_LineShader(RenderPass pass){
  CShaderPrg * shaderPrg = Get_LineShader(pass);
  return Setup_DefaultShader(shaderPrg, nullptr, nullptr);
}

CShaderPrg *CShaderMgr::Enable_SurfaceShader(RenderPass pass){
  CShaderPrg * shaderPrg = Get_SurfaceShader(pass);
  return Setup_DefaultShader(shaderPrg, nullptr, nullptr);
}

CShaderPrg *CShaderMgr::Enable_ConnectorShader(RenderPass pass){
  CShaderPrg * shaderPrg = Get_ConnectorShader(pass);
  if (!shaderPrg)
    return nullptr;
  shaderPrg = Setup_DefaultShader(shaderPrg, nullptr, nullptr);
  shaderPrg->SetLightingEnabled(0);
  {
    float front, back;
    front = SceneGetCurrentFrontSafe(G);
    back = SceneGetCurrentBackSafe(G);
    shaderPrg->Set1f("front", front);
    shaderPrg->Set1f("clipRange", back - front);
  }

  auto extent = SceneGetExtentStereo(G);
  shaderPrg->Set2f("screenSize", extent.width, extent.height);

  {
    float v_scale = SceneGetScreenVertexScale(G, nullptr);
    shaderPrg->Set1f("screenOriginVertexScale", v_scale/2.f);
  }

  return shaderPrg;
}

CShaderPrg *CShaderMgr::Setup_DefaultShader(CShaderPrg * shaderPrg,
    const CSetting *set1,
    const CSetting *set2) {
  if (!shaderPrg){
    current_shader = nullptr; 
    return shaderPrg;
  }
  shaderPrg->Enable();
  shaderPrg->SetBgUniforms();
  shaderPrg->Set_Stereo_And_AnaglyphMode();

  bool two_sided_lighting_enabled = SceneGetTwoSidedLightingSettings(G, set1, set2);

  shaderPrg->SetLightingEnabled(1); // lighting on by default
  shaderPrg->Set1i("two_sided_lighting_enabled", two_sided_lighting_enabled);
  shaderPrg->Set1f("ambient_occlusion_scale", 0.f);
  shaderPrg->Set1i("accessibility_mode", SettingGetGlobal_i(G, cSetting_ambient_occlusion_mode) / 4);
  shaderPrg->Set1f("accessibility_mode_on", SettingGetGlobal_i(G, cSetting_ambient_occlusion_mode) ? 1.f : 0.f);

  // interior_color
  {
    int interior_color = SettingGet_i(G, set1, set2, cSetting_ray_interior_color);
    if (interior_color == cColorDefault || two_sided_lighting_enabled) {
      shaderPrg->Set1i("use_interior_color", 0);
    } else {
      float inter[] = { 0.f, 0.f, 0.f };
      ColorGetEncoded(G, interior_color, inter);
      shaderPrg->Set1i("use_interior_color", 1);
      shaderPrg->Set4f("interior_color", inter[0], inter[1], inter[2], 1.f);
    }
  }

  shaderPrg->Set_Specular_Values();
  shaderPrg->Set_Matrices();
  return (shaderPrg);
}
CShaderPrg *CShaderMgr::Enable_CylinderShader(RenderPass pass){
  return Enable_CylinderShader("cylinder", pass);
}

CShaderPrg *CShaderMgr::Enable_CylinderShader(const char *shader_name, RenderPass pass){
  CShaderPrg *shaderPrg;

  auto extent = SceneGetExtentStereo(G);
  shaderPrg = GetShaderPrg(shader_name, 1, pass);
  if (!shaderPrg)
      return nullptr;
  shaderPrg->Enable();

  shaderPrg->SetLightingEnabled(1); // lighting on by default

  shaderPrg->Set1f("uni_radius", 0.f);

  shaderPrg->Set_Stereo_And_AnaglyphMode();

  shaderPrg->Set1f("inv_height", 1.0f / extent.height);
  shaderPrg->Set1i("no_flat_caps", 1);
  {
    float smooth_half_bonds = (SettingGetGlobal_i(G, cSetting_smooth_half_bonds)) ? .2f : 0.f;
    shaderPrg->Set1f("half_bond", smooth_half_bonds);
  }
  shaderPrg->Set_Specular_Values();
  shaderPrg->Set_Matrices();

  shaderPrg->SetBgUniforms();

  // always enable backface culling for cylinders
  glCullFace(GL_BACK);
  glEnable(GL_CULL_FACE);
  return shaderPrg;
}

CShaderPrg *CShaderMgr::Get_DefaultSphereShader(RenderPass pass){
  return GetShaderPrg("sphere", 1, pass);
}


CShaderPrg *CShaderMgr::Enable_DefaultSphereShader(RenderPass pass) {
  CShaderPrg *shaderPrg = Get_DefaultSphereShader(pass);
  if (!shaderPrg) return nullptr;
  shaderPrg->Enable();
  shaderPrg->SetLightingEnabled(1);
  shaderPrg->Set1f("sphere_size_scale", 1.f);

  shaderPrg->Set_Stereo_And_AnaglyphMode();

  shaderPrg->Set_Specular_Values();
  shaderPrg->Set_Matrices();

  shaderPrg->SetBgUniforms();

  return (shaderPrg);
}

CShaderPrg *CShaderMgr::Get_ConnectorShader(RenderPass pass){
  return GetShaderPrg("connector", 1, pass);
}

CShaderPrg *CShaderMgr::Get_DefaultShader(RenderPass pass){
  return GetShaderPrg("default", 1, pass);
}

CShaderPrg *CShaderMgr::Get_LineShader(RenderPass pass){
  return GetShaderPrg("line", 1, pass);
}

CShaderPrg *CShaderMgr::Get_SurfaceShader(RenderPass pass){
  return GetShaderPrg("surface", 1, pass);
}

CShaderPrg *CShaderMgr::Get_CylinderShader(RenderPass pass, short set_current_shader) {
  return GetShaderPrg("cylinder", set_current_shader, pass);
}

CShaderPrg *CShaderMgr::Get_CylinderNewShader(RenderPass pass, short set_current_shader) {
  return GetShaderPrg("cylinder_new", set_current_shader, pass);
}

CShaderPrg *CShaderMgr::Get_Current_Shader(){
  return current_shader;
}

CShaderPrg *CShaderMgr::Get_BackgroundShader(){
  return GetShaderPrg("bg");
}

CShaderPrg *CShaderMgr::Enable_BackgroundShader(){
  CShaderPrg * shaderPrg = Get_BackgroundShader();
  if (!shaderPrg) return shaderPrg;
  shaderPrg->Enable();

  glDisable(GL_DEPTH_TEST);
  shaderPrg->SetBgUniforms();

  return shaderPrg;
}

CShaderPrg *CShaderMgr::Enable_TriLinesShader() {
  CShaderPrg * shaderPrg = GetShaderPrg("trilines");
  if (!shaderPrg) return shaderPrg;
  shaderPrg->Enable();
  shaderPrg->SetBgUniforms();
  shaderPrg->Set_Stereo_And_AnaglyphMode();
  shaderPrg->Set_Matrices();
  auto extent = SceneGetExtentStereo(G);
  shaderPrg->Set2f("inv_dimensions", 1.f/extent.width, 1.f/extent.height);
  return shaderPrg;
}

#ifndef _PYMOL_NO_AA_SHADERS
#endif

CShaderPrg *CShaderMgr::Enable_OITShader() {
  CShaderPrg * shaderPrg = GetShaderPrg("oit");
  if (!shaderPrg) return shaderPrg;
  shaderPrg->Enable();

  constexpr GLuint accumTexUnit = 5;
  constexpr GLuint revealageTexUnit = 6;
  oit_pp->activateRTAsTexture(OIT_PostProcess::OITRT::ACCUM, accumTexUnit);
  oit_pp->activateRTAsTexture(OIT_PostProcess::OITRT::REVEALAGE, revealageTexUnit);
  shaderPrg->Set1i("accumTex", accumTexUnit);
  shaderPrg->Set1i("revealageTex", revealageTexUnit);
  shaderPrg->Set1f("isRight", stereo_flag > 0 ? 1. : 0);
  glEnable(GL_BLEND);
  glBlendFuncSeparate(
      GL_SRC_ALPHA,   GL_ONE_MINUS_SRC_ALPHA,
      GL_ONE,         GL_ONE_MINUS_SRC_ALPHA);

  glDisable(GL_DEPTH_TEST);
#ifndef PURE_OPENGL_ES_2
  glDisable(GL_ALPHA_TEST);
#endif
  return shaderPrg;
}

CShaderPrg *CShaderMgr::Enable_OITCopyShader() {
  CShaderPrg * shaderPrg = GetShaderPrg("copy");
  if (!shaderPrg) return shaderPrg;
  shaderPrg->Enable();

  constexpr GLuint colorTexUnit = 7;
  activateOffscreenTexture(colorTexUnit);
  shaderPrg->Set1i("colorTex", colorTexUnit);
  if (G->ShaderMgr->stereo_blend){
    // for full-screen stereo
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
  } else {
    glDisable(GL_BLEND);
  }
  glDisable(GL_DEPTH_TEST);
#ifndef PURE_OPENGL_ES_2
  glDisable(GL_ALPHA_TEST);
#endif
  return shaderPrg;
}

CShaderPrg *CShaderMgr::Enable_LabelShader(RenderPass pass){
  CShaderPrg *shaderPrg;
  shaderPrg = Get_LabelShader(pass);  
  if (!shaderPrg)
      return nullptr;
  shaderPrg->Enable();
  return Setup_LabelShader(shaderPrg);
}

CShaderPrg *CShaderMgr::Enable_ScreenShader(){
  CShaderPrg *shaderPrg;
  shaderPrg = Get_ScreenShader();  
  if (!shaderPrg)
      return nullptr;
  shaderPrg->Enable();

  int ortho_width, ortho_height;
  std::tie(ortho_width, ortho_height) = OrthoGetSize(*G->Ortho);
  shaderPrg->Set2f("t2PixelSize", 2.f / ortho_width, 2.f / ortho_height);

  return Setup_LabelShader(shaderPrg);
}

CShaderPrg *CShaderMgr::Enable_RampShader(){
  CShaderPrg *shaderPrg;
  shaderPrg = Get_RampShader();  
  if (!shaderPrg)
      return nullptr;
  shaderPrg->Enable();
  return Setup_LabelShader(shaderPrg);
}

CShaderPrg *CShaderMgr::Setup_LabelShader(CShaderPrg *shaderPrg) {
  shaderPrg->Set_Matrices();

  glActiveTexture(GL_TEXTURE3);
  TextureBindTexture(G);
  if (!(shaderPrg->uniform_set & 8)){
    shaderPrg->uniform_set |= 8;
    shaderPrg->Set1i("textureMap", 3);
  }

  auto extent = SceneGetExtentStereo(G);

  shaderPrg->Set2f("screenSize", extent.width, extent.height);

  shaderPrg->SetBgUniforms();

  {
    float v_scale = SceneGetScreenVertexScale(G, nullptr);
    shaderPrg->Set1f("screenOriginVertexScale", v_scale/2.f);
  }
  {
    float front, back;
    front = SceneGetCurrentFrontSafe(G);
    back = SceneGetCurrentBackSafe(G);
    shaderPrg->Set1f("front", front);
    shaderPrg->Set1f("clipRange", back - front);
  }

  return shaderPrg;
}

CShaderPrg *CShaderMgr::Get_LabelShader(RenderPass pass){
  return GetShaderPrg("label", 1, pass);
}

CShaderPrg *CShaderMgr::Get_ScreenShader() {
  if (is_picking)
    return nullptr;
  return GetShaderPrg("screen");
}

CShaderPrg *CShaderMgr::Get_RampShader() {
  return GetShaderPrg("ramp");
}

CShaderPrg *CShaderMgr::Get_IndicatorShader() {
  return GetShaderPrg("indicator");
}

CShaderPrg *CShaderMgr::Enable_IndicatorShader() {
  CShaderPrg * shaderPrg = Get_IndicatorShader();
  if (!shaderPrg) return shaderPrg;
  shaderPrg->Enable();

  shaderPrg->Set_Stereo_And_AnaglyphMode();
  shaderPrg->Set_Matrices();

  glActiveTexture(GL_TEXTURE3);
  TextureBindTexture(G);
  if (!(shaderPrg->uniform_set & 8)){
    shaderPrg->Set1i("textureMap", 3);
    shaderPrg->uniform_set |= 8;
  }
#ifdef PURE_OPENGL_ES_2
  shaderPrg->SetMat4fc("g_ModelViewMatrix", SceneGetModelViewMatrixPtr(G));
  shaderPrg->SetMat4fc("g_ProjectionMatrix", SceneGetProjectionMatrixPtr(G));
#endif

  return (shaderPrg);
}

CShaderPrg* CShaderMgr::Enable_BezierShader()
{
  auto shaderPrg = Get_BezierShader();
  if (!shaderPrg) {
    return nullptr;
  }
  shaderPrg->Enable();

  glPatchParameteri(GL_PATCH_VERTICES, 4);

  const float segmentCount = 256.0f;
  const float stripCount = 1.0f;

  shaderPrg->Set1f("segmentCount", segmentCount);
  shaderPrg->Set1f("stripCount", stripCount);

  shaderPrg->SetMat4fc("g_ModelViewMatrix", SceneGetModelViewMatrixPtr(G));
  shaderPrg->SetMat4fc("g_ProjectionMatrix", SceneGetProjectionMatrixPtr(G));
  return shaderPrg;
}

CShaderPrg* CShaderMgr::Get_BezierShader()
{
  return GetShaderPrg("bezier");
}

void CShaderMgr::ResetUniformSet() {
  for (auto & prog : programs) {
    prog.second->uniform_set = 0;
  }
}

void CShaderMgr::SetIsPicking(int is_picking) {
  this->is_picking = is_picking;
}
int CShaderMgr::GetIsPicking() {
  return is_picking;
}

#define LIGHTINGTEXTUREWIDTH 64

/*
 * Lighting setting indices are not contiguous, so we need a mapping array
 */
int light_setting_indices[] = {
  cSetting_light, cSetting_light2, cSetting_light3, cSetting_light4,
  cSetting_light5, cSetting_light6, cSetting_light7, cSetting_light8,
  cSetting_light9
};

/**
 * Generate and upload a precomputed vec2(ambient, specular) lighting texture.
 *
 * Must be equivalent to "ComputeLighting" in "compute_color_for_light.fs"
 */
void CShaderMgr::Generate_LightingTexture() {
  const int light_max = 10;
  int light_count = SettingGetGlobal_i(G, cSetting_light_count);
  int spec_count = SettingGetGlobal_i(G, cSetting_spec_count);
  float ambient = SettingGetGlobal_f(G, cSetting_ambient);
  float direct = SettingGetGlobal_f(G, cSetting_direct);
  float reflect = SettingGetGlobal_f(G, cSetting_reflect) * SceneGetReflectScaleValue(G, light_max);
  float shininess, spec_value;
  float shininess_0, spec_value_0;
  float diffuse, spec, shine;
  float power, power_0 = SettingGetGlobal_f(G, cSetting_power);
  float reflect_power = SettingGetGlobal_f(G, cSetting_reflect_power);

  float light_positions[light_max][3] = {{0.F, 0.F, 1.F}};

  // (ambient, specular) 2D texture
  unsigned char texture_AS[LIGHTINGTEXTUREWIDTH][LIGHTINGTEXTUREWIDTH][2];

  SceneGetAdjustedLightValues(G,
      &spec_value,
      &shininess,
      &spec_value_0,
      &shininess_0,
      light_max);

  if (light_count < 2) {
    light_count = 1;
    direct += reflect;
  } else if (light_count > light_max) {
    light_count = light_max;
  }

  if(spec_count < 0) {
    spec_count = light_count - 1;
  }

  for (int i = 1; i < light_count; ++i) {
    const float * setting = SettingGetGlobal_3fv(G, light_setting_indices[i - 1]);
    copy3f(setting, light_positions[i]);
    normalize3f(light_positions[i]);
    invert3f(light_positions[i]);
  }

  glGenTextures(1, &lightingTexture);

  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_CUBE_MAP, lightingTexture);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifndef PURE_OPENGL_ES_2
  glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
#else
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#endif

  float normal[3];
  const float vz = LIGHTINGTEXTUREWIDTH / 2;

  for (int face = 0; face < 6; ++face) {
    for (int y = 0; y < LIGHTINGTEXTUREWIDTH; ++y) {
      for (int x = 0; x < LIGHTINGTEXTUREWIDTH; ++x) {

        float vx =   x + .5f - vz;
        float vy = -(y + .5f - vz);

        switch (face) {
          case 0: set3f(normal,  vz,  vy, -vx); break;
          case 1: set3f(normal, -vz,  vy,  vx); break;
          case 2: set3f(normal,  vx,  vz, -vy); break;
          case 3: set3f(normal,  vx, -vz,  vy); break;
          case 4: set3f(normal,  vx,  vy,  vz); break;
          case 5: set3f(normal, -vx,  vy, -vz); break;
        }

        normalize3f(normal);

        float ambient_sum = ambient;
        float specular_sum = 0.F;

        for (int i = 0; i < light_count; ++i) {
          if (i == 0) {
            diffuse = direct;
            spec = spec_value_0;
            shine = shininess_0;
            power = power_0;
          } else {
            diffuse = reflect;
            spec = spec_value;
            shine = shininess;
            power = reflect_power;
          }

          // light direction (normalized)
          const float * L = light_positions[i];

          // cosine of angle between normal and light
          float NdotL = dot_product3f(normal, L);

          // normal points away from light
          if (NdotL <= 0.0)
            continue;

          // power/reflect_power, was ray trace only until 1.7.7
          NdotL = pow(NdotL, power);

          // diffuse
          ambient_sum += NdotL * diffuse;

          // specular
          if (i <= spec_count) {
            // H = normalize(L + vec3(0., 0., 1.));
            float H[] = {0., 0., 1.};
            add3f(L, H, H);
            normalize3f(H);

            float NdotH = std::max(dot_product3f(normal, H), 0.f);
            specular_sum += spec * pow(NdotH, shine);
          }
        }

        texture_AS[y][x][0] = pymol_roundf(255.F * std::min(1.F, ambient_sum));
        texture_AS[y][x][1] = pymol_roundf(255.F * std::min(1.F, specular_sum));
      }
    }

    glTexImage2D(
        GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
        /* level */ 0,
        /* internalformat */ GL_LUMINANCE_ALPHA,
        /* width  */ LIGHTINGTEXTUREWIDTH,
        /* height */ LIGHTINGTEXTUREWIDTH,
        /* border */ 0,
        /* format */ GL_LUMINANCE_ALPHA,
        /* type */ GL_UNSIGNED_BYTE,
        /* data */ (void*) texture_AS);
  }
}

void CShaderMgr::Set_Reload_Bits(int bits){
  reload_bits |= bits;
}

void CShaderMgr::Check_Reload() {
  if(!SettingGetGlobal_b(G, cSetting_use_shaders)) {
    return;
  }

  if (reload_bits){
    if (reload_bits == RELOAD_ALL_SHADERS) {
      for (auto& prog : programs)
        prog.second->is_valid = false;
      m_shaderPreprocessor.clear();
    }

    Reload_All_Shaders();
    reload_bits = 0;
  }
}

GLfloat *CShaderMgr::GetLineWidthRange() {
  return line_width_range;
}

#ifndef _PYMOL_NO_AA_SHADERS
#endif

/**
 * Register filename -> shader dependencies for `shader`
 */
void CShaderMgr::RegisterDependantFileNames(CShaderPrg * shader) {
  shader_deps[shader->vertfile].push_back(shader->name);
  shader_deps[shader->fragfile].push_back(shader->name);
  if (shader->geomParams) {
    shader_deps[shader->geomParams->file].push_back(shader->name);
  }
  if (shader->tessParams) {
    shader_deps[shader->tessParams->controlFile].push_back(shader->name);
    shader_deps[shader->tessParams->evaluationFile].push_back(shader->name);
  }
}

/**
 * Recursive function to insert `filename` and all the files where
 * `filename` is included into the given output vector.
 */
void CShaderMgr::CollectDependantFileNames(const std::string &filename,
    std::vector<std::string> &filenames) {
  auto it = include_deps.find(filename);
  if (it != include_deps.end()) {
    for (const char ** filenameptr = it->second;
        *filenameptr; ++filenameptr) {
      CollectDependantFileNames(*filenameptr, filenames);
    }
  }
  filenames.push_back(filename);
}

/**
 * Make derived shaders for all shaders that depend on `variable`
 */
void CShaderMgr::MakeDerivatives(const std::string &suffix, const std::string &variable) {
  std::set<std::string> shadernames;
  std::vector<std::string> filenames;

  // variable -> files
  for (const char ** filenameptr = ifdef_deps[variable];
      *filenameptr; ++filenameptr) {
    CollectDependantFileNames(*filenameptr, filenames);
  }

  // files -> shaders
  for (auto& filename : filenames) {
    auto &vec = shader_deps[filename];
    for (auto& n_it : vec) {
      shadernames.insert(n_it);
    }
  }

  // create shader derivatives
  for (const auto& shadername : shadernames) {
    auto shader = programs[shadername]->DerivativeCopy(shadername + suffix, variable);
    programs[shader->name] = shader;

    // register dependency
    RegisterDependantFileNames(shader);
  }
}

/**
 * Reload the derivative shaders for `variable`
 */
void CShaderMgr::Reload_Derivatives(const std::string &variable, bool value) {
  SetPreprocVar(variable, value, false);

  for (auto& prog : programs) {
    if (prog.second->derivative == variable)
      prog.second->reload();
  }

  SetPreprocVar(variable, !value, false);
}

/**
 * Removes `filename` and all it's parents from the shader source cache,
 * and if `invshaders` is true, also clear the `is_valid` flag for all
 * shader infos that depend on `filename`.
 */
void CShaderMgr::ShaderSourceInvalidate(const char * filename, bool invshaders) {
  // recursion for includes
  auto it = include_deps.find(filename);
  if (it != include_deps.end()) {
    for (const char ** filenameptr = it->second;
        *filenameptr; ++filenameptr) {
      ShaderSourceInvalidate(*filenameptr, invshaders);
    }
  }

  // invalidate shaders
  if (invshaders) {
    auto &vec = shader_deps[filename];
    for (const auto& shadername : vec) {
      programs[shadername]->is_valid = false;
    }
  }

  m_shaderPreprocessor.invalidate(filename);
}

/**
 * Set the value for the `#ifdef` variable `key` and if the value has changed,
 * then invalidate all its dependant shader source files.
 */
void CShaderMgr::SetPreprocVar(const std::string &key, bool value, bool invshaders) {
  auto& ref = m_shaderPreprocessor.getVar(key);
  if (ref != value) {
    for (const char ** filenameptr = ifdef_deps[key];
        *filenameptr; ++filenameptr) {
      ShaderSourceInvalidate(*filenameptr, invshaders);
    }
    ref = value;
  }
}

/**
 * Insert `filename` -> `contents` (processed source) into the shader source
 * cache and invalidate its parents
 */
void CShaderMgr::SetShaderSource(const char * filename, const std::string &contents) {
  ShaderSourceInvalidate(filename);
  m_shaderPreprocessor.setSource(filename, contents);
}

void CShaderMgr::bindGPUBuffer(size_t hashid) {
  auto search = _gpu_object_map.find(hashid);
  if (search != _gpu_object_map.end())
    search->second->bind();
}

void CShaderMgr::freeGPUBuffer(size_t hashid) {
  if (!hashid)
    return;
  LOCK_GUARD_MUTEX(lock, gpu_objects_to_free_mutex);
  _gpu_objects_to_free_vector.push_back(hashid);

#ifdef _WEBGL
  freeAllGPUBuffers(); // immediate free on web
#endif
}

void CShaderMgr::freeGPUBuffers(std::vector<size_t> &&hashids) {
  LOCK_GUARD_MUTEX(lock, gpu_objects_to_free_mutex);
  _gpu_objects_to_free_vector.insert(_gpu_objects_to_free_vector.end(),
                                     hashids.begin(), hashids.end());
#ifdef _WEBGL
  freeAllGPUBuffers(); // immediate free on web
#endif
}

void CShaderMgr::freeGPUBuffers(size_t * arr, size_t len) {
  for (unsigned int i = 0; i < len; ++i)
    freeGPUBuffer(arr[i]);
}

void CShaderMgr::freeAllGPUBuffers() {
  LOCK_GUARD_MUTEX(lock, gpu_objects_to_free_mutex);
  for (auto hashid : _gpu_objects_to_free_vector) {
    auto search = _gpu_object_map.find(hashid);
    if (search != _gpu_object_map.end()) {
      if (search->second)
        delete search->second;
      _gpu_object_map.erase(search);
    }
  }
  _gpu_objects_to_free_vector.clear();
}

int CShaderMgr::GetAttributeUID(const char * name)
{
  auto uloc = attribute_uids_by_name.find(name);
  if (uloc != attribute_uids_by_name.end())
    return uloc->second;

  int uid = attribute_uids_by_name.size() + 1;
  attribute_uids_by_name[name] = uid;
  attribute_uids[uid] = name;
  return uid;
}
const char *CShaderMgr::GetAttributeName(int uid)
{
  auto uloc = attribute_uids.find(uid);
  if (uloc == attribute_uids.end())
    return nullptr;
  return attribute_uids[uid].c_str();
}

// SceneRenderBindToOffscreen
void CShaderMgr::bindOffscreen(int width, int height, GridInfo *grid) {
  using namespace tex;
  renderTarget_t::shape_type req_size(width, height);
  renderTarget_t* rt = nullptr;

#ifndef _PYMOL_NO_AA_SHADERS
#endif

  // Doesn't exist, create
  if (!offscreen_rt) {
    CGOFree(G->Scene->offscreenCGO);
    rt = newGPUBuffer<renderTarget_t>(req_size);
    rt->layout({ { 4, rt_layout_t::UBYTE } });
    offscreen_rt = rt->get_hash_id();
  } else {
    rt = getGPUBuffer<renderTarget_t>(offscreen_rt);

    // resize
    if (req_size != rt->size()) {
      rt->resize(req_size);
#ifndef _PYMOL_NO_AA_SHADERS
#endif
    }
  }

  if (rt)
    rt->bind(!stereo_blend);
  glEnable(GL_BLEND);

  SceneInitializeViewport(G, true);
  if (grid->active) {
    grid->cur_view.offset = Offset2D{};
    grid->cur_view.extent.width = static_cast<std::uint32_t>(req_size.x);
    grid->cur_view.extent.height = static_cast<std::uint32_t>(req_size.y);
  }
}

// SceneRenderBindToOffscreenOIT
void CShaderMgr::bindOffscreenOIT(int width, int height, int drawbuf) {
  using namespace tex;

  renderTarget_t::shape_type req_size(width, height);

  if(!oit_pp || oit_pp->size() != req_size) {
    oit_pp = std::make_unique<OIT_PostProcess>(
        width, height, getGPUBuffer<renderTarget_t>(offscreen_rt)->_rbo);
  } else {
    if (!TM3_IS_ONEBUF) {
      drawbuf = 1;
    }
    oit_pp->bindFBORBO(drawbuf - 1);
  }
}

GLFramebufferConfig CShaderMgr::bindOffscreenOrtho(const Extent2D& extent, bool clear) {
  using namespace tex;
  renderTarget_t::shape_type req_size(extent.width, extent.height);
  if (!offscreen_ortho_rt) {
    auto rt = newGPUBuffer<renderTarget_t>(req_size);
    rt->layout({ { 4, rt_layout_t::UBYTE } });
    offscreen_ortho_rt = rt->get_hash_id();
  }

  auto rt = getGPUBuffer<renderTarget_t>(offscreen_ortho_rt);
  if (rt->size() != req_size) {
    freeGPUBuffer(offscreen_ortho_rt);
    rt = newGPUBuffer<renderTarget_t>(req_size);
    rt->layout({ { 4, rt_layout_t::UBYTE } });
    offscreen_ortho_rt = rt->get_hash_id();
  }
  rt->bind(clear);
  return GLFramebufferConfig{
      static_cast<std::uint32_t>(offscreen_ortho_rt), //
      GL_COLOR_ATTACHMENT0 //
  };
}

GLFramebufferConfig CShaderMgr::bindOffscreenSizedImage(
    const Extent2D& extent, bool clear)
{
  using namespace tex;
  renderTarget_t::shape_type req_size(extent.width, extent.height);
  if (!offscreen_sized_image_rt) {
    auto rt = newGPUBuffer<renderTarget_t>(req_size);
    rt->layout({{4, rt_layout_t::UBYTE}});
    offscreen_sized_image_rt = rt->get_hash_id();
  }
  auto rt = getGPUBuffer<renderTarget_t>(offscreen_sized_image_rt);
  if (rt->size() != req_size) {
    freeGPUBuffer(offscreen_sized_image_rt);
    rt = newGPUBuffer<renderTarget_t>(req_size);
    rt->layout({{4, rt_layout_t::UBYTE}});
    offscreen_sized_image_rt = rt->get_hash_id();
  }
  rt->bind(clear);
  return GLFramebufferConfig{
      static_cast<std::uint32_t>(offscreen_sized_image_rt), //
      GL_COLOR_ATTACHMENT0       //
  };
}

void CShaderMgr::activateOffscreenTexture(GLuint textureIdx) {
  glActiveTexture(GL_TEXTURE0 + textureIdx);
  auto t = getGPUBuffer<renderTarget_t>(offscreen_rt);
  if (t->_textures[0])
    t->_textures[0]->bind();
}

void CShaderMgr::Disable_Current_Shader()
{
  if(current_shader){
    current_shader->Disable();
  }
}

void CShaderMgr::setDrawBuffer(GLenum mode)
{
  if (mode == GL_BACK) {
    mode = G->ShaderMgr->defaultBackbuffer.drawBuffer;
  }

  if (!hasFrameBufferBinding() &&
      (mode != G->ShaderMgr->currentFBConfig.drawBuffer) && G->HaveGUI &&
      G->ValidContext) {
#ifndef PURE_OPENGL_ES_2
    glDrawBuffer(mode);
#endif
    G->ShaderMgr->currentFBConfig.drawBuffer = mode;
  }
}

void CShaderMgr::setDrawBuffer(GLFramebufferConfig config)
{
  if (config.drawBuffer == GL_BACK) {
    config = defaultBackbuffer;
  }
  if (config.framebuffer == defaultBackbuffer.framebuffer) {
    glBindFramebuffer(GL_FRAMEBUFFER, config.framebuffer);
    setDrawBuffer(config.drawBuffer);
    return;
  }

  if (auto rt = getGPUBuffer<renderTarget_t>(config.framebuffer)) {
    rt->bind(false);
  }
}

std::vector<unsigned char> CShaderMgr::readPixelsFrom(
    PyMOLGlobals* G, const Rect2D& rect, const GLFramebufferConfig& srcConfig)
{
  constexpr std::size_t pixelSizeBytes = 4;
  std::vector<unsigned char> dstPixels(
      rect.extent.width * rect.extent.height * pixelSizeBytes, 0);
  int prevReadFBO;
  int prevDrawFBO;
  int prevReadBuffer;
  glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prevReadFBO);
  glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prevDrawFBO);
  glGetIntegerv(GL_READ_BUFFER, &prevReadBuffer);

  if (srcConfig.framebuffer == OpenGLDefaultFramebufferID) {
    glBindFramebuffer(GL_READ_FRAMEBUFFER, OpenGLDefaultFramebufferID);
    glReadBuffer(srcConfig.drawBuffer);
  } else {
    if (auto rt = getGPUBuffer<renderTarget_t>(srcConfig.framebuffer)) {
      // TODO: bindOnlyAsRead
      rt->fbo()->bind();
    }
  }
  PyMOLReadPixels(rect.offset.x, rect.offset.y, rect.extent.width,
      rect.extent.height, GL_RGBA, GL_UNSIGNED_BYTE, dstPixels.data());

  // Restore State
  glBindFramebuffer(GL_READ_FRAMEBUFFER, prevReadFBO);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prevDrawFBO);
  glReadBuffer(prevReadBuffer);
  return dstPixels;
}

void CShaderMgr::drawPixelsTo(PyMOLGlobals* G, const Rect2D& rect,
    const std::byte* srcPixels, const GLFramebufferConfig& dstConfig)
{
  int prevReadFBO;
  int prevDrawFBO;
  int prevDrawBuffer;
  glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prevReadFBO);
  glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prevDrawFBO);
  glGetIntegerv(GL_READ_BUFFER, &prevDrawBuffer);

  if (dstConfig.framebuffer == OpenGLDefaultFramebufferID) {
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstConfig.framebuffer);
  } else {
    if (auto rt = getGPUBuffer<renderTarget_t>(dstConfig.framebuffer)) {
      // TODO: bindOnlyAsDraw?
      rt->fbo()->bind();
    }
  }
  glDrawBuffer(dstConfig.drawBuffer);
  PyMOLDrawPixels(rect.extent.width, rect.extent.height, GL_RGBA,
      GL_UNSIGNED_BYTE, srcPixels);
  // Restore State
  glBindFramebuffer(GL_READ_FRAMEBUFFER, prevReadFBO);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prevDrawFBO);
  glReadBuffer(prevDrawBuffer);
}
