/*========================================================================= Program: Visualization Toolkit Module: vtkOpenGLShaderCache.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkOpenGLShaderCache.h" #include "vtk_glew.h" #include "vtkObjectFactory.h" #include "vtkOpenGLError.h" #include "vtkOpenGLRenderWindow.h" #include "vtkShader.h" #include "vtkShaderProgram.h" #include "vtkOpenGLHelper.h" #include #include #include "vtksys/MD5.h" class vtkOpenGLShaderCache::Private { public: vtksysMD5* md5; // map of hash to shader program structs std::map ShaderPrograms; Private() { md5 = vtksysMD5_New(); } ~Private() { vtksysMD5_Delete(this->md5); } //----------------------------------------------------------------------------- void ComputeMD5(const char* content, const char* content2, const char* content3, std::string &hash) { unsigned char digest[16]; char md5Hash[33]; md5Hash[32] = '\0'; vtksysMD5_Initialize(this->md5); if (content) { vtksysMD5_Append(this->md5, reinterpret_cast(content), (int)strlen(content)); } if (content2) { vtksysMD5_Append(this->md5, reinterpret_cast(content2), (int)strlen(content2)); } if (content3) { vtksysMD5_Append(this->md5, reinterpret_cast(content3), (int)strlen(content3)); } vtksysMD5_Finalize(this->md5, digest); vtksysMD5_DigestToHex(digest, md5Hash); hash = md5Hash; } }; // ---------------------------------------------------------------------------- vtkStandardNewMacro(vtkOpenGLShaderCache); // ---------------------------------------------------------------------------- vtkOpenGLShaderCache::vtkOpenGLShaderCache() : Internal(new Private) { this->LastShaderBound = nullptr; } // ---------------------------------------------------------------------------- vtkOpenGLShaderCache::~vtkOpenGLShaderCache() { typedef std::map::const_iterator SMapIter; SMapIter iter = this->Internal->ShaderPrograms.begin(); for ( ; iter != this->Internal->ShaderPrograms.end(); ++iter) { iter->second->Delete(); } delete this->Internal; } // perform System and Output replacments unsigned int vtkOpenGLShaderCache::ReplaceShaderValues( std::string &VSSource, std::string &FSSource, std::string &GSSource) { // first handle renaming any Fragment shader inputs // if we have a geometry shader. By deafult fragment shaders // assume their inputs come from a Vertex Shader. When we // have a Geometry shader we rename the frament shader inputs // to come from the geometry shader if (!GSSource.empty()) { vtkShaderProgram::Substitute(FSSource,"VSOut","GSOut"); } #if GL_ES_VERSION_3_0 == 1 std::string version = "#version 300 es\n"; bool needFragDecls = true; #else std::string version = "#version 120\n"; bool needFragDecls = false; int glMajorVersion = 2; int glMinorVersion = 0; glGetIntegerv(GL_MAJOR_VERSION, & glMajorVersion); glGetIntegerv(GL_MINOR_VERSION, & glMinorVersion); if (glMajorVersion >= 3) { version = "#version 150\n"; if (glMajorVersion == 3 && glMinorVersion == 1) { version = "#version 140\n"; } else { needFragDecls = true; } } #endif vtkShaderProgram::Substitute(VSSource,"//VTK::System::Dec", version + "#ifdef GL_ES\n" "#if __VERSION__ == 300\n" "#define attribute in\n" "#define varying out\n"\ "#endif // 300\n" "#else // GL_ES\n" "#define highp\n" "#define mediump\n" "#define lowp\n" "#if __VERSION__ == 150\n" "#define attribute in\n" "#define varying out\n" "#endif\n" "#endif // GL_ES\n" ); vtkShaderProgram::Substitute(FSSource,"//VTK::System::Dec", version + "#ifdef GL_ES\n" "#if __VERSION__ == 300\n" "#define varying in\n" "#ifdef GL_FRAGMENT_PRECISION_HIGH\n" "precision highp float;\n" "precision highp sampler2D;\n" "precision highp sampler3D;\n" "#else\n" "precision mediump float;\n" "precision mediump sampler2D;\n" "precision mediump sampler3D;\n" "#endif\n" "#define texelFetchBuffer texelFetch\n" "#define texture1D texture\n" "#define texture2D texture\n" "#define texture3D texture\n" "#endif // 300\n" "#else // GL_ES\n" "#define highp\n" "#define mediump\n" "#define lowp\n" "#if __VERSION__ == 150\n" "#define varying in\n" "#define texelFetchBuffer texelFetch\n" "#define texture1D texture\n" "#define texture2D texture\n" "#define texture3D texture\n" "#endif\n" "#if __VERSION__ == 120\n" "#extension GL_EXT_gpu_shader4 : require\n" "#endif\n" "#endif // GL_ES\n" ); vtkShaderProgram::Substitute(GSSource,"//VTK::System::Dec", version + "#ifdef GL_ES\n" "#ifdef GL_FRAGMENT_PRECISION_HIGH\n" "precision highp float;\n" "#else\n" "precision mediump float;\n" "#endif\n" "#else // GL_ES\n" "#define highp\n" "#define mediump\n" "#define lowp\n" "#if __VERSION__ == 150\n" "#define attribute in\n" "#define varying out\n" "#endif\n" "#endif // GL_ES\n" ); if (needFragDecls) { unsigned int count = 0; std::string fragDecls; bool done = false; while (!done) { std::ostringstream src; std::ostringstream dst; src << "gl_FragData[" << count << "]"; // this naming has to match the bindings // in vtkOpenGLShaderProgram.cxx dst << "fragOutput" << count; done = !vtkShaderProgram::Substitute(FSSource, src.str(),dst.str()); if (!done) { #if GL_ES_VERSION_3_0 src.str(""); src.clear(); src << count; fragDecls += "layout(location = " + src.str() + ") "; #endif fragDecls += "out vec4 " + dst.str() + ";\n"; count++; } } vtkShaderProgram::Substitute(FSSource,"//VTK::Output::Dec",fragDecls); return count; } return 0; } vtkShaderProgram *vtkOpenGLShaderCache::ReadyShaderProgram( std::map shaders, vtkTransformFeedback *cap) { std::string VSSource = shaders[vtkShader::Vertex]->GetSource(); std::string FSSource = shaders[vtkShader::Fragment]->GetSource(); std::string GSSource = shaders[vtkShader::Geometry]->GetSource(); unsigned int count = this->ReplaceShaderValues(VSSource,FSSource,GSSource); shaders[vtkShader::Vertex]->SetSource(VSSource); shaders[vtkShader::Fragment]->SetSource(FSSource); shaders[vtkShader::Geometry]->SetSource(GSSource); vtkShaderProgram *shader = this->GetShaderProgram(shaders); shader->SetNumberOfOutputs(count); return this->ReadyShaderProgram(shader, cap); } // return nullptr if there is an issue vtkShaderProgram *vtkOpenGLShaderCache::ReadyShaderProgram( const char *vertexCode, const char *fragmentCode, const char *geometryCode, vtkTransformFeedback *cap) { // perform system wide shader replacements // desktops to not use percision statements std::string VSSource = vertexCode; std::string FSSource = fragmentCode; std::string GSSource = geometryCode; unsigned int count = this->ReplaceShaderValues(VSSource,FSSource,GSSource); vtkShaderProgram *shader = this->GetShaderProgram( VSSource.c_str(), FSSource.c_str(), GSSource.c_str()); shader->SetNumberOfOutputs(count); return this->ReadyShaderProgram(shader, cap); } // return nullptr if there is an issue vtkShaderProgram *vtkOpenGLShaderCache::ReadyShaderProgram( vtkShaderProgram *shader, vtkTransformFeedback *cap) { if (!shader) { return nullptr; } if (shader->GetTransformFeedback() != cap) { this->ReleaseCurrentShader(); shader->ReleaseGraphicsResources(nullptr); shader->SetTransformFeedback(cap); } // compile if needed if (!shader->GetCompiled() && !shader->CompileShader()) { return nullptr; } // bind if needed if (!this->BindShader(shader)) { return nullptr; } return shader; } vtkShaderProgram *vtkOpenGLShaderCache::GetShaderProgram( std::map shaders) { // compute the MD5 and the check the map std::string result; this->Internal->ComputeMD5( shaders[vtkShader::Vertex]->GetSource().c_str(), shaders[vtkShader::Fragment]->GetSource().c_str(), shaders[vtkShader::Geometry]->GetSource().c_str(), result); // does it already exist? typedef std::map::const_iterator SMapIter; SMapIter found = this->Internal->ShaderPrograms.find(result); if (found == this->Internal->ShaderPrograms.end()) { // create one vtkShaderProgram *sps = vtkShaderProgram::New(); sps->SetVertexShader(shaders[vtkShader::Vertex]); sps->SetFragmentShader(shaders[vtkShader::Fragment]); sps->SetGeometryShader(shaders[vtkShader::Geometry]); sps->SetMD5Hash(result); // needed? this->Internal->ShaderPrograms.insert(std::make_pair(result, sps)); return sps; } else { return found->second; } } vtkShaderProgram *vtkOpenGLShaderCache::GetShaderProgram( const char *vertexCode, const char *fragmentCode, const char *geometryCode) { // compute the MD5 and the check the map std::string result; this->Internal->ComputeMD5(vertexCode, fragmentCode, geometryCode, result); // does it already exist? typedef std::map::const_iterator SMapIter; SMapIter found = this->Internal->ShaderPrograms.find(result); if (found == this->Internal->ShaderPrograms.end()) { // create one vtkShaderProgram *sps = vtkShaderProgram::New(); sps->GetVertexShader()->SetSource(vertexCode); sps->GetFragmentShader()->SetSource(fragmentCode); if (geometryCode != nullptr) { sps->GetGeometryShader()->SetSource(geometryCode); } sps->SetMD5Hash(result); // needed? this->Internal->ShaderPrograms.insert(std::make_pair(result, sps)); return sps; } else { return found->second; } } void vtkOpenGLShaderCache::ReleaseGraphicsResources(vtkWindow *win) { // NOTE: // In the current implementation as of October 26th, if a shader // program is created by ShaderCache then it should make sure // that it releases the graphics resouces used by these programs. // It is not wisely for callers to do that since then they would // have to loop over all the programs were in use and invoke // release graphics resources individually. this->ReleaseCurrentShader(); typedef std::map::const_iterator SMapIter; SMapIter iter = this->Internal->ShaderPrograms.begin(); for ( ; iter != this->Internal->ShaderPrograms.end(); ++iter) { iter->second->ReleaseGraphicsResources(win); } } void vtkOpenGLShaderCache::ReleaseCurrentShader() { // release prior shader if (this->LastShaderBound) { this->LastShaderBound->Release(); this->LastShaderBound = nullptr; } } int vtkOpenGLShaderCache::BindShader(vtkShaderProgram* shader) { if (this->LastShaderBound == shader) { return 1; } // release prior shader if (this->LastShaderBound) { this->LastShaderBound->Release(); } shader->Bind(); this->LastShaderBound = shader; return 1; } // ---------------------------------------------------------------------------- void vtkOpenGLShaderCache::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); }