layer0/ShaderMgr.cpp (1,380 lines of code) (raw):

/* 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 "OOMac.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 "File.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 */ using namespace std; #ifndef _DEAD_CODE_DIE #define SUPPRESS_GEOMETRY_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 string &strobj){ const GLchar *str = (const GLchar *)strobj.c_str(); glShaderSource(shad, 1, (const GLchar **)&str, NULL); } 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; string gs, vs, fs; CShaderMgr *I = G->ShaderMgr; GLint status; if (!geomfile.empty()) gs = I->GetShaderSource(geomfile); 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 (!gs.empty() && SettingGetGlobal_b(G, cSetting_use_geometry_shaders)) { if (!gid) { gid = glCreateShader(GL_GEOMETRY_SHADER); GLenum err; if ((err=glGetError()) || !gid) { 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, gid); } glShaderSource1String(gid, gs); glCompileShader((GLuint) gid); glGetShaderiv(gid, GL_COMPILE_STATUS, &status); if (!status) { #ifndef SUPPRESS_GEOMETRY_SHADER_ERRORS ErrorMsgWithShaderInfoLog(gid, "geometry shader compilation failed."); #endif glDetachShader(id, gid); glDeleteShader(gid); gid = 0; return false; } glProgramParameteriEXT(id, GL_GEOMETRY_INPUT_TYPE_EXT, gsInput); glProgramParameteriEXT(id, GL_GEOMETRY_OUTPUT_TYPE_EXT, gsOutput); glProgramParameteriEXT(id, GL_GEOMETRY_VERTICES_OUT_EXT, ngsVertsOut); PRINTFB(G, FB_ShaderMgr, FB_Debugging) " ShaderPrg-Debug: geometry shader compiled.\n" ENDFB(G); } else if (gid) { // for manually switching off geometry shaders (set use_geometry_shaders, off) glDetachShader(id, gid); glDeleteShader(gid); gid = 0; } WARNING_IF_GLERROR("CShaderPrg::reload after geometry shader"); #endif // 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; void getGLVersion(PyMOLGlobals * G, int *major, int* minor); void getGLSLVersion(PyMOLGlobals * G, int* major, int* minor); static void disableShaders(PyMOLGlobals * G); #ifdef WIN32 /* REMOVE US */ PFNGLTEXIMAGE3DPROC getTexImage3D(){ static PFNGLTEXIMAGE3DPROC my_glTexImage3D = NULL; if (!my_glTexImage3D) my_glTexImage3D = (PFNGLTEXIMAGE3DPROC) wglGetProcAddress("glTexImage3D"); return my_glTexImage3D; } #endif /* * 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, 0); } static void disableGeometryShaders(PyMOLGlobals * G) { SettingSetGlobal_b(G, cSetting_use_geometry_shaders, 0); if(G->ShaderMgr) G->ShaderMgr->SetPreprocVar("use_geometry_shaders", 0); if (G->Option && !G->Option->quiet) PRINTFB(G, FB_ShaderMgr, FB_Warnings) " Geometry 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 string stringReplaceAll(const string &src, const string * replaceStrings) { 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)) != 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); ostringstream accstr; string rawtemplate = GetShaderSource("call_compute_color_for_light.fs"); 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++){ 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 (map<string, CShaderPrg*>::iterator it = programs.begin(); it != programs.end(); ++it) { it->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 it = programs.begin(); it != programs.end(); ++it) { if (it->second->derivative.empty()) it->second->reload(); } } // bitmasks for preprocessor parsing #define IFDEF 1 // #ifdef or #ifndef #define IFNDEF 2 // #ifndef #define ELSE 4 // #else #define ENDIF 8 // #endif #define INCLUDE 16 // #include #define LOOKUP 32 // #ifdef or #ifndef or #include // preprocessor directive (like '#ifdef') -> bitmask static map<string, short> preprocmap; // filename -> contents (static filesystem) static map<string, const char *> shader_cache_raw; // preproc variable -> NULL terminated list of filenames ("used by") std::map<std::string, const char **> ifdef_deps; // filename -> NULL terminated list of filenames ("included by") std::map<std::string, const char **> include_deps; /* * Return a pointer to the next whitespace character or to the end of the string */ static const char * nextwhitespace(const char * p) { for (;; p++) { switch (*p) { case ' ': case '\0': case '\n': case '\r': case '\t': return p; } } } /* * Return a pointer to the next line beginning or to the end of the string. * Skips blank lines. */ static const char * nextline(const char * p) { for (;; p++) { switch (*p) { case '\0': case '\n': case '\r': goto switch2; } } for (;; p++) { switch2: switch (*p) { case ' ': case '\n': case '\r': case '\t': break; default: return p; } } } /* * Get the processed shader file contents with all #ifdef and #include * preprocessors processed. * * Note: There must be a single whitespace character between preprocessor * directive and argument. * * Valid: * #ifdef foo * * Invalid: * # ifdef foo * #ifdef foo * * Function arguments: * filename: file name of the shader file inside $PYMOL_DATA/shaders */ string CShaderMgr::GetShaderSource(const string &filename) { // processed cache auto it = shader_cache_processed.find(filename); if (it != shader_cache_processed.end()) { return it->second; } char* buffer = NULL; const char *pl = NULL, *newpl, *tpl; std::ostringstream newbuffer; /* "if_depth" counts the level of nesting, and "true_depth" how far the * if conditions were actually true. So if the current block is true, then * if_depth == true_depth, otherwise if_depth > true_depth. */ int if_depth = 0, true_depth = 0; #ifndef _PYMOL_IOS /* read the file from disk */ if (SettingGetGlobal_b(G, cSetting_shaders_from_disk)) { const char * pymol_data = getenv("PYMOL_DATA"); if (pymol_data && pymol_data[0]) { string path(pymol_data); path.append(PATH_SEP).append("shaders").append(PATH_SEP).append(filename); pl = buffer = FileGetContents(path.c_str(), NULL); if (!buffer) { PRINTFB(G, FB_ShaderMgr, FB_Warnings) " Warning: shaders_from_dist=on, but unable to open file '%s'\n", path.c_str() ENDFB(G); } } else { PRINTFB(G, FB_ShaderMgr, FB_Warnings) " Warning: shaders_from_dist=on, but PYMOL_DATA not set\n" ENDFB(G); } } #endif if (!pl) { pl = shader_cache_raw[filename]; if (!pl) { PRINTFB(G, FB_ShaderMgr, FB_Errors) " GetShaderSource-Error: No such file: '%s'\n", filename.c_str() ENDFB(G); return ""; } } /* Now we need to read through the shader and do processing if necessary */ for (; *pl; pl = newpl) { int preproc = 0; // only do preprocessor lookup if line starts with a hash if (pl[0] == '#') { // next white space tpl = nextwhitespace(pl); // copy of first word string tmp_str(pl, tpl - pl); // lookup word in preprocmap map<string, short>::const_iterator preprocit = preprocmap.find(tmp_str); if (preprocit != preprocmap.end()) { preproc = preprocit->second; if (preproc & LOOKUP) { // #ifdef or #ifndef or #include if (if_depth == true_depth) { // copy of second word tpl++; tmp_str = string(tpl, nextwhitespace(tpl) - tpl); if (preproc & IFDEF) { // #ifdef or #ifndef bool if_value = false; // lookup for boolean shader preprocessor values auto item = preproc_vars.find(tmp_str); if (item != preproc_vars.end()) if_value = item->second; if (preproc & IFNDEF) if_value = !if_value; // #ifndef if (if_value) true_depth++; } else if (preproc & INCLUDE) { //#include tmp_str = string(tpl, nextwhitespace(tpl) - tpl); newbuffer << GetShaderSource(tmp_str); } } if (preproc & IFDEF) if_depth++; } else if (preproc & ENDIF){ // #endif if (if_depth-- == true_depth) true_depth--; } else if (preproc & ELSE){ // #else if (if_depth == true_depth) true_depth--; else if (if_depth == true_depth + 1) true_depth++; } } } newpl = nextline(pl); // add to the output buffer if this is a regular active line if (!preproc && if_depth == true_depth) { newbuffer.write(pl, newpl - pl); } } FreeP(buffer); string result = newbuffer.str(); shader_cache_processed[filename] = result; return result; } #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, NULL, NULL, cSetting_bg_image_filename); short bg_image = bg_image_filename && bg_image_filename[0]; bg_image_mode_solid = !(bg_gradient || bg_image || OrthoBackgroundDataIsSet(G)); 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)); } #ifndef PYMOL_EDU 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); SetPreprocVar("ANAGLYPH", (stereo && stereo_mode==cStereo_anaglyph) ? 1 : 0); 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 (preprocmap.empty()) { preprocmap["#ifdef"] = LOOKUP | IFDEF; preprocmap["#ifndef"] = LOOKUP | IFDEF | IFNDEF; preprocmap["#else"] = ELSE; preprocmap["#endif"] = ENDIF; preprocmap["#include"] = LOOKUP | INCLUDE; // 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); for (int i = 0; i < 3; ++i) G->ShaderMgr->offscreen_rt[i] = 0; for (int i = 0; i < 2; ++i) G->ShaderMgr->oit_rt[i] = 0; 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); vector<GLchar> infoLog(infoLogLength); glGetShaderInfoLog(sid, infoLogLength, NULL, 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(); 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) { FeedbackAdd(G, " Shaders available.\n"); } else { FeedbackAdd(G, " Shaders and volumes unavailable.\n"); disableShaders(G); return; } } else { /* print info on glew error? */ FeedbackAdd(G, " 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 preproc_vars["GLEW_VERSION_3_0"] = GLEW_VERSION_3_0 ? 1 : 0; if (TM3_IS_ONEBUF){ preproc_vars["ONE_DRAW_BUFFER"] = 1; } #ifdef PURE_OPENGL_ES_2 preproc_vars["PURE_OPENGL_ES_2"] = 1; preproc_vars["PYMOL_WEBGL"] = 1; preproc_vars["PYMOL_WEBGL_IOS"] = 1; #else preproc_vars["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){ make_program("connector", "connector.vs", "connector.fs", "connector.gs", GL_POINTS, GL_TRIANGLE_STRIP, CONNECTOR_GS_NUM_VERTICES); } else { make_program("connector", "connector.vs", "connector.fs"); } 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 (programs["connector"]->reload() && programs["connector"]->gid) { shaders_present |= MASK_SHADERS_PRESENT_GEOMETRY; } else { disableGeometryShaders(G); } #else SettingSetGlobal_b(G, cSetting_use_geometry_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 it = programs.begin(); it != programs.end(); ++it) { RegisterDependantFileNames(it->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); FeedbackAdd(G, buf); } #endif shaders_present |= 0x1; SettingSetGlobal_b(G, cSetting_use_shaders, 1); 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==NULL) || (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!=NULL) && (strstr(extstr, "GL_ARB_shading_language_100")!=NULL)){ *major = 1; *minor = 0; } } /* GL > version 1 */ else if (gl_major>=2) { const char* verstr = (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION); if ((verstr==NULL) || (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_) { G = G_; current_shader = 0; 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 it = programs.begin(); it != programs.end(); ++it) { delete it->second; } programs.clear(); shader_cache_processed.clear(); for (int i = 0; i < 3; ++i) freeGPUBuffer(offscreen_rt[i]); for (int i = 0; i < 2; ++i) freeGPUBuffer(oit_rt[i]); freeGPUBuffer(areatex); freeGPUBuffer(searchtex); FreeAllVBOs(); } int CShaderMgr::AddShaderPrg(CShaderPrg * s) { if (!s) return 0; const string& name = s->name; if (programs.find(name)!=programs.end()){ delete programs[name]; } programs[name] = s; return 1; } int CShaderMgr::RemoveShaderPrg(const 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, short pass) { if (pass < 0 && SettingGetGlobal_i(G, cSetting_transparency_mode) == 3) { name += "_t"; } auto it = programs.find(name); if (it == programs.end()) return NULL; 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, int pass) { CShaderPrg * shaderPrg = Get_DefaultShader(pass); return Setup_DefaultShader(shaderPrg, set1, set2); } CShaderPrg *CShaderMgr::Enable_DefaultShader(int pass){ CShaderPrg * shaderPrg = Get_DefaultShader(pass); return Setup_DefaultShader(shaderPrg, NULL, NULL); } CShaderPrg *CShaderMgr::Enable_LineShader(int pass){ CShaderPrg * shaderPrg = Get_LineShader(pass); return Setup_DefaultShader(shaderPrg, NULL, NULL); } CShaderPrg *CShaderMgr::Enable_SurfaceShader(int pass){ CShaderPrg * shaderPrg = Get_SurfaceShader(pass); return Setup_DefaultShader(shaderPrg, NULL, NULL); } CShaderPrg *CShaderMgr::Enable_ConnectorShader(int pass){ CShaderPrg * shaderPrg = Get_ConnectorShader(pass); if (!shaderPrg) return 0; shaderPrg = Setup_DefaultShader(shaderPrg, NULL, NULL); shaderPrg->SetLightingEnabled(0); { float front, back; front = SceneGetCurrentFrontSafe(G); back = SceneGetCurrentBackSafe(G); shaderPrg->Set1f("front", front); shaderPrg->Set1f("clipRange", back - front); } int width, height; SceneGetWidthHeightStereo(G, &width, &height); shaderPrg->Set2f("screenSize", width, height); { float v_scale = SceneGetScreenVertexScale(G, NULL); 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 = NULL; 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(int pass){ return Enable_CylinderShader("cylinder", pass); } CShaderPrg *CShaderMgr::Enable_CylinderShader(const char *shader_name, int pass){ int width, height; CShaderPrg *shaderPrg; SceneGetWidthHeightStereo(G, &width, &height); shaderPrg = GetShaderPrg(shader_name, 1, pass); if (!shaderPrg) return NULL; 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.0/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(int pass){ return GetShaderPrg("sphere", 1, pass); } CShaderPrg *CShaderMgr::Enable_DefaultSphereShader(int pass) { CShaderPrg *shaderPrg = Get_DefaultSphereShader(pass); if (!shaderPrg) return NULL; 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); } #ifdef _PYMOL_ARB_SHADERS CShaderPrg *CShaderMgr::Enable_SphereShaderARB(){ CShaderPrg *shaderPrg = NULL; /* load the vertex program */ if (current_shader) current_shader->Disable(); shaderPrg = GetShaderPrg("sphere_arb"); glBindProgramARB(GL_VERTEX_PROGRAM_ARB, shaderPrg->vid); /* load the fragment program */ glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, shaderPrg->fid); /* load some safe initial values */ glProgramEnvParameter4fARB(GL_VERTEX_PROGRAM_ARB, 0, 0.0F, 0.0F, 1.0F, 0.0F); glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0.5F, 2.0F, 0.0F, 0.0F); glEnable(GL_VERTEX_PROGRAM_ARB); glEnable(GL_FRAGMENT_PROGRAM_ARB); return shaderPrg; } #endif CShaderPrg *CShaderMgr::Get_ConnectorShader(int pass){ return GetShaderPrg("connector", 1, pass); } CShaderPrg *CShaderMgr::Get_DefaultShader(int pass){ return GetShaderPrg("default", 1, pass); } CShaderPrg *CShaderMgr::Get_LineShader(int pass){ return GetShaderPrg("line", 1, pass); } CShaderPrg *CShaderMgr::Get_SurfaceShader(int pass){ return GetShaderPrg("surface", 1, pass); } CShaderPrg *CShaderMgr::Get_CylinderShader(int pass, short set_current_shader) { return GetShaderPrg("cylinder", set_current_shader, pass); } CShaderPrg *CShaderMgr::Get_CylinderNewShader(int 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(); { int width, height; SceneGetWidthHeightStereo(G, &width, &height); shaderPrg->Set2f("inv_dimensions", 1.f/width, 1.f/height); } return shaderPrg; } #ifndef _PYMOL_NO_AA_SHADERS #endif CShaderPrg *CShaderMgr::Enable_OITShader() { CShaderPrg * shaderPrg = GetShaderPrg("oit"); if (!shaderPrg) return shaderPrg; shaderPrg->Enable(); glActiveTexture(GL_TEXTURE5); bindOffscreenOITTexture(0); glActiveTexture(GL_TEXTURE6); bindOffscreenOITTexture(1); shaderPrg->Set1i("accumTex", 5); shaderPrg->Set1i("revealageTex", 6); 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(); glActiveTexture(GL_TEXTURE7); bindOffscreenTexture(0); shaderPrg->Set1i("colorTex", 7); 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(int pass){ CShaderPrg *shaderPrg; shaderPrg = Get_LabelShader(pass); if (!shaderPrg) return NULL; shaderPrg->Enable(); return Setup_LabelShader(shaderPrg); } CShaderPrg *CShaderMgr::Enable_ScreenShader(){ CShaderPrg *shaderPrg; shaderPrg = Get_ScreenShader(); if (!shaderPrg) return NULL; shaderPrg->Enable(); int ortho_width, ortho_height; OrthoGetSize(G, &ortho_width, &ortho_height); 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 NULL; shaderPrg->Enable(); return Setup_LabelShader(shaderPrg); } CShaderPrg *CShaderMgr::Setup_LabelShader(CShaderPrg *shaderPrg) { int width = 0, height = 0; shaderPrg->Set_Matrices(); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, TextureGetTextTextureID(G)); if (!(shaderPrg->uniform_set & 8)){ shaderPrg->uniform_set |= 8; shaderPrg->Set1i("textureMap", 3); } SceneGetWidthHeightStereo(G, &width, &height); if (width) shaderPrg->Set2f("screenSize", width, height); shaderPrg->SetBgUniforms(); { float v_scale = SceneGetScreenVertexScale(G, NULL); 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(int pass){ return GetShaderPrg("label", 1, pass); } CShaderPrg *CShaderMgr::Get_ScreenShader() { if (is_picking) return NULL; 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); glBindTexture(GL_TEXTURE_2D, TextureGetTextTextureID(G)); if (!(shaderPrg->uniform_set & 8)){ shaderPrg->Set1i("textureMap", 3); shaderPrg->uniform_set |= 8; } #ifdef PURE_OPENGL_ES_2 shaderPrg->SetMat4fc("g_ModelViewMatrix", SceneGetModelViewMatrix(G)); shaderPrg->SetMat4fc("g_ProjectionMatrix", SceneGetProjectionMatrix(G)); #endif return (shaderPrg); } void CShaderMgr::ResetUniformSet() { for (map<string, CShaderPrg*>::iterator it = programs.begin(), end = programs.end(); it != end; ++it) { it->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 it = programs.begin(); it != programs.end(); ++it) it->second->is_valid = false; shader_cache_processed.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->geomfile.empty()) shader_deps[shader->geomfile].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 f_it = filenames.begin(); f_it != filenames.end(); ++f_it) { auto &vec = shader_deps[*f_it]; for (auto n_it = vec.begin(); n_it != vec.end(); ++n_it) { shadernames.insert(*n_it); } } // create shader derivatives for (auto s_it = shadernames.begin(); s_it != shadernames.end(); ++s_it) { CShaderPrg * shader = programs[*s_it]->DerivativeCopy(*s_it + 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 it = programs.begin(); it != programs.end(); ++it) { if (it->second->derivative == variable) it->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 (auto it = vec.begin(); it != vec.end(); ++it) { programs[*it]->is_valid = false; } } // invalidate source file auto repl_it = shader_cache_processed.find(filename); if (repl_it != shader_cache_processed.end()) { shader_cache_processed.erase(repl_it); } } /* * 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 = preproc_vars[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); shader_cache_processed[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 NULL; return attribute_uids[uid].c_str(); } // SceneRenderBindToOffscreen void CShaderMgr::bindOffscreen(int width, int height, GridInfo *grid) { using namespace tex; ivec2 req_size(width, height); #ifndef _PYMOL_NO_AA_SHADERS #endif // Doesn't exist, create if (!offscreen_rt[0]) { CGOFree(G->Scene->offscreenCGO); offscreen_size = req_size; auto rt0 = newGPUBuffer<renderTarget_t>(req_size); rt0->layout({ { 4, rt_layout_t::UBYTE } }); offscreen_rt[0] = rt0->get_hash_id(); auto rt1 = newGPUBuffer<renderTarget_t>(req_size); rt1->layout({ { 4, rt_layout_t::UBYTE } }); offscreen_rt[1] = rt1->get_hash_id(); auto rt2 = newGPUBuffer<renderTarget_t>(req_size); rt2->layout({ { 4, rt_layout_t::UBYTE } }); offscreen_rt[2] = rt2->get_hash_id(); } else { // resize if (req_size != offscreen_size) { for (int i = 0; i < 3; ++i) getGPUBuffer<renderTarget_t>(offscreen_rt[i])->resize(req_size); offscreen_size = req_size; } } auto rt = getGPUBuffer<renderTarget_t>(offscreen_rt[0]); if (rt) rt->bind(!stereo_blend); glEnable(GL_BLEND); SceneInitializeViewport(G, 1); if (grid->active) { grid->cur_view[0] = grid->cur_view[1] = 0; grid->cur_view[2] = offscreen_size.x; grid->cur_view[3] = offscreen_size.y; } } // SceneRenderBindToOffscreenOIT void CShaderMgr::bindOffscreenOIT(int width, int height, int drawbuf) { using namespace tex; ivec2 req_size(width, height); if (!oit_rt[0] || (req_size != oit_size)) { if (oit_rt[0]) { freeGPUBuffers({ oit_rt[0], oit_rt[1] }); } if (TM3_IS_ONEBUF){ auto rt0 = newGPUBuffer<renderTarget_t>(req_size); rt0->layout({ { 4, rt_layout_t::FLOAT } }, getGPUBuffer<renderTarget_t>(offscreen_rt[0])->_rbo); oit_rt[0] = rt0->get_hash_id(); auto rt1 = newGPUBuffer<renderTarget_t>(req_size); rt1->layout({ { 1, rt_layout_t::FLOAT } }, rt0->_rbo); oit_rt[1] = rt1->get_hash_id(); } else { std::vector<rt_layout_t> layout; layout.emplace_back(4, rt_layout_t::FLOAT); if (GLEW_VERSION_3_0) layout.emplace_back(1, rt_layout_t::FLOAT); else layout.emplace_back(2, rt_layout_t::FLOAT); auto rt0 = newGPUBuffer<renderTarget_t>(req_size); rt0->layout(std::move(layout), getGPUBuffer<renderTarget_t>(offscreen_rt[0])->_rbo); oit_rt[0] = rt0->get_hash_id(); } oit_size = req_size; } else { if (!TM3_IS_ONEBUF){ drawbuf = 1; } getGPUBuffer<renderTarget_t>(oit_rt[drawbuf - 1])->_fbo->bind(); getGPUBuffer<renderTarget_t>(oit_rt[drawbuf - 1])->_rbo->bind(); } } void CShaderMgr::bindOffscreenFBO(int index) { bool clear = true; if (index == 0) clear = !stereo_blend; auto t = getGPUBuffer<renderTarget_t>(offscreen_rt[index]); if (t) t->bind(clear); } void CShaderMgr::bindOffscreenOITFBO(int index) { #if !defined(PURE_OPENGL_ES_2) || defined(_WEBGL) if (TM3_IS_ONEBUF){ auto rt = getGPUBuffer<renderTarget_t>(oit_rt[index - 1]); if (rt) rt->_fbo->bind(); } else { const GLenum bufs[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }; auto rt = getGPUBuffer<renderTarget_t>(oit_rt[0]); if (rt) rt->_fbo->bind(); glDrawBuffers(2, bufs); } glClearColor(0.f, 0.f, 0.f, 0.f); glClear(GL_COLOR_BUFFER_BIT); glDepthMask(GL_FALSE); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFuncSeparate( GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); #endif } void CShaderMgr::bindOffscreenTexture(int index) { auto t = getGPUBuffer<renderTarget_t>(offscreen_rt[index]); if (t->_textures[0]) t->_textures[0]->bind(); } void CShaderMgr::bindOffscreenOITTexture(int index) { if (TM3_IS_ONEBUF){ auto t = getGPUBuffer<renderTarget_t>(oit_rt[index]); if (t->_textures[0]) t->_textures[0]->bind(); } else { auto t = getGPUBuffer<renderTarget_t>(oit_rt[0]); if (t) t->_textures[index]->bind(); } }