// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkGLSLModPixelDebugger.h" #include "vtkActor.h" #include "vtkCellGridMapper.h" #include "vtkDrawTexturedElements.h" #include "vtkInformation.h" #include "vtkInformationObjectBaseKey.h" #include "vtkLightingMapPass.h" #include "vtkLogger.h" #include "vtkObjectFactory.h" #include "vtkOpenGLRenderer.h" #include "vtkProperty.h" #include "vtkSetGet.h" #include "vtkShaderProgram.h" #include "vtkStringToken.h" #include "vtk_nlohmannjson.h" #include VTK_NLOHMANN_JSON(json.hpp) #include #include #include #include #include #include // Silence a warning that to_json() (generated by the invocation // of NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE() below) is unused. #if defined(__GNUC__) #pragma GCC diagnostic ignored "-Wunused-function" #endif VTK_ABI_NAMESPACE_BEGIN namespace { //------------------------------------------------------------------------------ struct SubstitutionData { std::string Target; std::string ShaderType; std::string FileName; bool ReplaceAllOccurrences = false; bool FileNameIsAbsolute = false; bool Enabled = true; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SubstitutionData, Target, ShaderType, FileName, ReplaceAllOccurrences, FileNameIsAbsolute, Enabled) //------------------------------------------------------------------------------ std::vector ParseSubstitutions( vtkGLSLModPixelDebugger* mod, std::string& jsonFileName) { using namespace nlohmann; json jsonObject; if (!vtksys::SystemTools::FileExists(jsonFileName)) { // silently ignore files that do not exist. return {}; } try { std::ifstream file(jsonFileName); jsonObject = json::parse(file); } catch (json::parse_error& ex) { vtkErrorWithObjectMacro(mod, << "Failed to parse " << jsonFileName << " with error \'" << ex.what() << "\' at byte " << ex.byte); return {}; } if (!jsonObject.contains("Substitutions")) { vtkErrorWithObjectMacro( mod, << "Failed to find a list named \'Substitutions\'. Here's a sample json file " "that is valid: \n" << R"({ "Substitutions": [ { "Target": "//VTK::Light::Impl", "ShaderType": "Fragment", "FileName": "normal-debug.glsl", "ReplaceAllOccurrences": false, "FileNameIsAbsolute": false, "Enabled": false }, { "Target": "//VTK::Light::Impl", "ShaderType": "Fragment", "FileName": "parametric-debug.glsl", "ReplaceAllOccurrences": false, "FileNameIsAbsolute": false, "Enabled": false } ] } )"); return {}; } auto substitutionObjects = jsonObject.at("Substitutions"); int idx = -1; std::vector substitutions; for (auto& substObject : substitutionObjects) { SubstitutionData substData = {}; ++idx; try { substData = substObject.get<::SubstitutionData>(); } catch (json::exception& ex) { vtkErrorWithObjectMacro(mod, << "Substitution Idx [" << idx << "] - Failed to deserialize \'" << substObject.dump() << "\' with error \'" << ex.what()); continue; } substitutions.emplace_back(substData); } return substitutions; } //------------------------------------------------------------------------------ std::string GetGLSLFileContentsOnlyIfEnabled( const SubstitutionData& subst, const std::string& jsonFileName) { auto path = vtksys::SystemTools::GetFilenamePath(jsonFileName); if (subst.Enabled) { std::string glslFileName = subst.FileName; // resolve path if not absolute. if (!subst.FileNameIsAbsolute) { glslFileName = vtksys::SystemTools::JoinPath({ path, "/", subst.FileName }); } // read the glsl file contents std::ifstream file(glslFileName); std::stringstream contents; contents << file.rdbuf(); return contents.str(); } // disabled substitutions, treat it as if there is no glsl. return ""; } } //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkGLSLModPixelDebugger); //------------------------------------------------------------------------------ vtkGLSLModPixelDebugger::vtkGLSLModPixelDebugger() { // load up a default based on source location. auto path = vtksys::SystemTools::GetFilenamePath(__FILE__); this->SubstitutionJSONFileName = vtksys::SystemTools::JoinPath({ path, "/", "LiveGLSLDebugSample", "sample.json" }); if (!vtksys::SystemTools::FileExists(this->SubstitutionJSONFileName)) { vtkDebugMacro(<< "Running without source repository." << "The default json file " << this->SubstitutionJSONFileName << " does not exist. "); } } //------------------------------------------------------------------------------ vtkGLSLModPixelDebugger::~vtkGLSLModPixelDebugger() = default; //------------------------------------------------------------------------------ void vtkGLSLModPixelDebugger::PrintSelf(ostream& os, vtkIndent indent) { os << "SubstitutionJSONFileName: " << this->SubstitutionJSONFileName; this->Superclass::PrintSelf(os, indent); } //------------------------------------------------------------------------------ bool vtkGLSLModPixelDebugger::ReplaceShaderValues(vtkOpenGLRenderer* vtkNotUsed(renderer), std::string& vertexShader, std::string& tessControlShader, std::string& tessEvalShader, std::string& geometryShader, std::string& fragmentShader, vtkAbstractMapper* vtkNotUsed(mapper), vtkActor* vtkNotUsed(actor)) { auto substs = ::ParseSubstitutions(this, this->SubstitutionJSONFileName); int idx = -1; for (const auto& subst : substs) { auto glslContents = ::GetGLSLFileContentsOnlyIfEnabled(subst, this->SubstitutionJSONFileName); ++idx; if (glslContents.empty()) { if (subst.Enabled) { // warn if user made a mistake in the filename or gave no glsl code. vtkWarningMacro(<< "Substitutions[" << idx << "] was enabled but glsl code was empty! Maybe incorrect filename?"); } continue; } if (subst.ShaderType == "Fragment") { vtkShaderProgram::Substitute( fragmentShader, subst.Target, glslContents, subst.ReplaceAllOccurrences); } else if (subst.ShaderType == "Geometry") { vtkShaderProgram::Substitute( geometryShader, subst.Target, glslContents, subst.ReplaceAllOccurrences); } else if (subst.ShaderType == "Vertex") { vtkShaderProgram::Substitute( vertexShader, subst.Target, glslContents, subst.ReplaceAllOccurrences); } else if (subst.ShaderType == "TessControl") { vtkShaderProgram::Substitute( tessControlShader, subst.Target, glslContents, subst.ReplaceAllOccurrences); } else if (subst.ShaderType == "TessEvaluation") { vtkShaderProgram::Substitute( tessEvalShader, subst.Target, glslContents, subst.ReplaceAllOccurrences); } } // std::cout << "VS:" << vertexShader << std::endl; // std::cout << "TCS:" << tessControlShader << std::endl; // std::cout << "TES:" << tessEvalShader << std::endl; // std::cout << "GS:" << geometryShader << std::endl; // std::cout << "FS:" << fragmentShader << std::endl; this->LastSubstitutionJSONFileContentsToken = this->HashSubstitutionJSONFileContents(); this->LastGLSLFilesContentsToken = this->HashGLSLFilesContents(); return true; } //------------------------------------------------------------------------------ bool vtkGLSLModPixelDebugger::SetShaderParameters(vtkOpenGLRenderer* vtkNotUsed(renderer), vtkShaderProgram* vtkNotUsed(program), vtkAbstractMapper* vtkNotUsed(mapper), vtkActor* vtkNotUsed(actor), vtkOpenGLVertexArrayObject* vtkNotUsed(VAO) /*=nullptr*/) { return true; } //------------------------------------------------------------------------------ vtkStringToken vtkGLSLModPixelDebugger::HashSubstitutionJSONFileContents() { std::ifstream file(this->SubstitutionJSONFileName); std::stringstream contents; contents << file.rdbuf(); return contents.str(); } //------------------------------------------------------------------------------ vtkStringToken vtkGLSLModPixelDebugger::HashGLSLFilesContents() { auto substs = ::ParseSubstitutions(this, this->SubstitutionJSONFileName); std::string glslContents; for (const auto& subst : substs) { glslContents += ::GetGLSLFileContentsOnlyIfEnabled(subst, this->SubstitutionJSONFileName); } return glslContents; } VTK_ABI_NAMESPACE_END