/* 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 #include #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 #include #include #include #include /* 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(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(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(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; iInvalidate(); } } 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 shader_cache_raw; // preproc variable -> nullptr terminated list of filenames ("used by") std::map ifdef_deps; // filename -> nullptr terminated list of filenames ("included by") std::map include_deps; std::string CShaderMgr::GetShaderSource(const std::string &filename) { return m_shaderPreprocessor.getSource(filename); } std::map* 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 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{"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{"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(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; i0) 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 &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 shadernames; std::vector 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 &&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(req_size); rt->layout({ { 4, rt_layout_t::UBYTE } }); offscreen_rt = rt->get_hash_id(); } else { rt = getGPUBuffer(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(req_size.x); grid->cur_view.extent.height = static_cast(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( width, height, getGPUBuffer(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(req_size); rt->layout({ { 4, rt_layout_t::UBYTE } }); offscreen_ortho_rt = rt->get_hash_id(); } auto rt = getGPUBuffer(offscreen_ortho_rt); if (rt->size() != req_size) { freeGPUBuffer(offscreen_ortho_rt); rt = newGPUBuffer(req_size); rt->layout({ { 4, rt_layout_t::UBYTE } }); offscreen_ortho_rt = rt->get_hash_id(); } rt->bind(clear); return GLFramebufferConfig{ static_cast(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(req_size); rt->layout({{4, rt_layout_t::UBYTE}}); offscreen_sized_image_rt = rt->get_hash_id(); } auto rt = getGPUBuffer(offscreen_sized_image_rt); if (rt->size() != req_size) { freeGPUBuffer(offscreen_sized_image_rt); rt = newGPUBuffer(req_size); rt->layout({{4, rt_layout_t::UBYTE}}); offscreen_sized_image_rt = rt->get_hash_id(); } rt->bind(clear); return GLFramebufferConfig{ static_cast(offscreen_sized_image_rt), // GL_COLOR_ATTACHMENT0 // }; } void CShaderMgr::activateOffscreenTexture(GLuint textureIdx) { glActiveTexture(GL_TEXTURE0 + textureIdx); auto t = getGPUBuffer(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(config.framebuffer)) { rt->bind(false); } } std::vector CShaderMgr::readPixelsFrom( PyMOLGlobals* G, const Rect2D& rect, const GLFramebufferConfig& srcConfig) { constexpr std::size_t pixelSizeBytes = 4; std::vector 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(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(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); }