// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #include #include #include #include #include #include #include #include namespace mx = MaterialX; namespace GenShaderUtil { const std::string LAYOUT_SUFFIX("_layout"); const std::string SOURCE_CODE_STRING("sourcecode"); namespace { const std::string& getFileExtensionForTarget(const std::string& target) { static const std::unordered_map _fileExtensions = { {"genglsl","glsl"}, {"genosl","osl"}, {"genmdl","mdl"} }; auto it = _fileExtensions.find(target); return it != _fileExtensions.end() ? it->second : target; } } bool getShaderSource(mx::GenContext& context, const mx::ImplementationPtr implementation, mx::FilePath& sourcePath, std::string& resolvedSource, std::string& sourceContents) { if (implementation) { resolvedSource = implementation->getAttribute(SOURCE_CODE_STRING); if (!resolvedSource.empty()) { return true; } sourcePath = implementation->getFile(); mx::FilePath localPath = mx::FilePath(implementation->getSourceUri()).getParentPath(); mx::FilePath resolvedPath = context.resolveSourceFile(sourcePath, localPath); sourceContents = mx::readFile(resolvedPath); resolvedSource = resolvedPath.asString(); return !sourceContents.empty(); } return false; } // Check that implementations exist for all nodedefs supported per generator void checkImplementations(mx::GenContext& context, const mx::StringSet& generatorSkipNodeTypes, const mx::StringSet& generatorSkipNodeDefs) { const mx::ShaderGenerator& shadergen = context.getShaderGenerator(); mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::DocumentPtr doc = mx::createDocument(); loadLibraries({ "libraries/targets", "libraries/stdlib", "libraries/pbrlib" }, searchPath, doc); const std::string& target = shadergen.getTarget(); std::string fileName = target + "_implementation_check.txt"; std::filebuf implDumpBuffer; implDumpBuffer.open(fileName, std::ios::out); std::ostream implDumpStream(&implDumpBuffer); context.registerSourceCodeSearchPath(searchPath); // Node types to explicitly skip temporarily. mx::StringSet skipNodeTypes = { "displacement", "volume", "conical_edf", "measured_edf", "absorption_vdf", "geompropvalue", "surfacematerial", "volumematerial" }; skipNodeTypes.insert(generatorSkipNodeTypes.begin(), generatorSkipNodeTypes.end()); // Explicit set of node defs to skip temporarily mx::StringSet skipNodeDefs = { "ND_add_vdf", "ND_multiply_vdfF", "ND_multiply_vdfC", "ND_mix_displacementshader", "ND_mix_volumeshader", "ND_mix_vdf", "ND_surfacematerial", "ND_volumematerial" }; skipNodeDefs.insert(generatorSkipNodeDefs.begin(), generatorSkipNodeDefs.end()); implDumpStream << "-----------------------------------------------------------------------" << std::endl; implDumpStream << "Scanning target: " << target << std::endl; std::vector impls = doc->getImplementations(); implDumpStream << "-----------------------------------------------------------------------" << std::endl; implDumpStream << "Scanning implementations: " << std::to_string(impls.size()) << std::endl; for (const auto& impl : impls) { mx::NodeDefPtr nodedef = impl->getNodeDef(); if (!nodedef) { std::string msg(impl->getName()); const std::string& targetName = impl->getTarget(); if (targetName.size()) { msg += ", target: " + targetName; } const std::string& nodedefName = impl->getNodeDefString(); msg += ": Missing nodedef with name: " + nodedefName; implDumpStream << msg << std::endl; } } std::string nodeDefNode; std::string nodeDefType; unsigned int count = 0; unsigned int missing = 0; unsigned int skipped = 0; std::string missing_str; std::string found_str; std::vector nodedefs = doc->getNodeDefs(); implDumpStream << "-----------------------------------------------------------------------" << std::endl; implDumpStream << "Scanning nodedefs: " << std::to_string(nodedefs.size()) << std::endl; // Scan through every nodedef defined for (mx::NodeDefPtr nodedef : nodedefs) { count++; const std::string& nodeDefName = nodedef->getName(); const std::string& nodeName = nodedef->getNodeString(); if (skipNodeTypes.count(nodeName)) { found_str += "Temporarily skipping implementation required for nodedef: " + nodeDefName + ", Node : " + nodeName + ".\n"; skipped++; continue; } if (skipNodeDefs.count(nodeDefName)) { found_str += "Temporarily skipping implementation required for nodedef: " + nodeDefName + ", Node : " + nodeName + ".\n"; skipped++; continue; } if (!requiresImplementation(nodedef)) { found_str += "No implementation required for nodedef: " + nodeDefName + ", Node: " + nodeName + ".\n"; continue; } mx::InterfaceElementPtr inter = nodedef->getImplementation(target); if (!inter) { missing++; missing_str += "Missing nodedef implementation: " + nodeDefName + ", Node: " + nodeName + ".\n"; std::vector inters = doc->getMatchingImplementations(nodeDefName); for (const auto& inter2 : inters) { mx::ImplementationPtr impl = inter2->asA(); if (impl) { std::string msg("\t Cached Impl: "); msg += impl->getName(); msg += ", nodedef: " + impl->getNodeDefString(); msg += ", target: " + impl->getTarget(); missing_str += msg + ".\n"; } } for (const auto& childImpl : impls) { if (childImpl->getNodeDefString() == nodeDefName) { std::string msg("\t Doc Impl: "); msg += childImpl->getName(); msg += ", nodedef: " + childImpl->getNodeDefString(); msg += ", target: " + childImpl->getTarget(); missing_str += msg + ".\n"; } } } else { mx::ImplementationPtr impl = inter->asA(); if (impl) { // Test if the generator has an internal implementation first if (shadergen.implementationRegistered(impl->getName())) { found_str += "Found generator impl for nodedef: " + nodeDefName + ", Node: " + nodeDefName + ". Impl: " + impl->getName() + ".\n"; } // Check for an implementation explicitly stored else { mx::FilePath sourcePath; std::string resolvedSource; std::string contents; if (!getShaderSource(context, impl, sourcePath, resolvedSource, contents)) { missing++; missing_str += "Missing source code: " + sourcePath.asString() + " for nodedef: " + nodeDefName + ". Impl: " + impl->getName() + ".\n"; } else { found_str += "Found impl and src for nodedef: " + nodeDefName + ", Node: " + nodeName + +". Impl: " + impl->getName() + ". Source: " + resolvedSource + ".\n"; } } } else { mx::NodeGraphPtr graph = inter->asA(); found_str += "Found NodeGraph impl for nodedef: " + nodeDefName + ", Node: " + nodeName + ". Graph Impl: " + graph->getName(); mx::InterfaceElementPtr graphNodeDefImpl = graph->getImplementation(); if (graphNodeDefImpl) { found_str += ". Graph Nodedef Impl: " + graphNodeDefImpl->getName(); } found_str += ".\n"; } } } implDumpStream << "Missing: " << missing << " implementations out of: " << count << " nodedefs. Skipped: " << skipped << std::endl; implDumpStream << missing_str << std::endl; implDumpStream << found_str << std::endl; // Should have 0 missing including skipped if (missing != 0) { std::cerr << (std::string("Missing: ") + std::to_string(missing) + std::string(" implementations out of: ") + std::to_string(count) + std::string(" nodedefs. Skipped: ") + std::to_string(skipped)) << std::endl; std::cerr << (std::string("Missing list: ") + missing_str) << std::endl; } REQUIRE(missing == 0); implDumpBuffer.close(); } void testUniqueNames(mx::GenContext& context, const std::string& stage) { mx::DocumentPtr doc = mx::createDocument(); mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); loadLibraries({ "libraries/targets", "libraries/stdlib" }, searchPath, doc); const std::string exampleName = "unique_names"; // Generate a shader with an internal node having the same name as the shader, // which will result in a name conflict between the shader output and the // internal node output const std::string shaderName = "unique_names"; const std::string nodeName = shaderName; mx::NodeGraphPtr nodeGraph = doc->addNodeGraph("IMP_" + exampleName); mx::OutputPtr output1 = nodeGraph->addOutput("out", "color3"); mx::NodePtr node1 = nodeGraph->addNode("noise2d", nodeName, "color3"); output1->setConnectedNode(node1); const mx::ShaderGenerator& shadergen = context.getShaderGenerator(); // Set the output to a restricted name const std::string& outputQualifier = shadergen.getSyntax().getOutputQualifier(); output1->setName(outputQualifier); mx::GenOptions options; mx::ShaderPtr shader = shadergen.generate(shaderName, output1, context); REQUIRE(shader != nullptr); REQUIRE(shader->getSourceCode(stage).length() > 0); // Make sure the output and internal node output has their variable names set const mx::ShaderGraphOutputSocket* sgOutputSocket = shader->getGraph().getOutputSocket(); REQUIRE(sgOutputSocket->getVariable() != outputQualifier); const mx::ShaderNode* sgNode1 = shader->getGraph().getNode(node1->getName()); REQUIRE(sgNode1->getOutput()->getVariable() == "unique_names_out"); } // Test ShaderGen performance void shaderGenPerformanceTest(mx::GenContext& context) { mx::DocumentPtr nodeLibrary = mx::createDocument(); const mx::FileSearchPath libSearchPath(mx::getDefaultDataSearchPath()); // Load the standard libraries. loadLibraries({ "libraries" }, libSearchPath, nodeLibrary); context.registerSourceCodeSearchPath(libSearchPath); // Enable Color Management mx::ColorManagementSystemPtr colorManagementSystem = mx::DefaultColorManagementSystem::create(context.getShaderGenerator().getTarget()); REQUIRE(colorManagementSystem); if (colorManagementSystem) { context.getShaderGenerator().setColorManagementSystem(colorManagementSystem); colorManagementSystem->loadLibrary(nodeLibrary); } // Enable Unit System mx::UnitSystemPtr unitSystem = mx::UnitSystem::create(context.getShaderGenerator().getTarget()); REQUIRE(unitSystem); if (unitSystem) { context.getShaderGenerator().setUnitSystem(unitSystem); unitSystem->loadLibrary(nodeLibrary); // Setup Unit converters unitSystem->setUnitConverterRegistry(mx::UnitConverterRegistry::create()); mx::UnitTypeDefPtr distanceTypeDef = nodeLibrary->getUnitTypeDef("distance"); unitSystem->getUnitConverterRegistry()->addUnitConverter(distanceTypeDef, mx::LinearUnitConverter::create(distanceTypeDef)); mx::UnitTypeDefPtr angleTypeDef = nodeLibrary->getUnitTypeDef("angle"); unitSystem->getUnitConverterRegistry()->addUnitConverter(angleTypeDef, mx::LinearUnitConverter::create(angleTypeDef)); context.getOptions().targetDistanceUnit = "meter"; } // Read mtlx documents mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::FilePathVec testRootPaths; testRootPaths.push_back(searchPath.find("resources/Materials/Examples/StandardSurface")); std::vector loadedDocuments; mx::StringVec documentsPaths; mx::StringVec errorLog; for (const auto& testRoot : testRootPaths) { mx::loadDocuments(testRoot, libSearchPath, {}, {}, loadedDocuments, documentsPaths, nullptr, &errorLog); } REQUIRE(loadedDocuments.size() > 0); REQUIRE(loadedDocuments.size() == documentsPaths.size()); // Shuffle the order of documents and perform document library import validation and shadergen std::mt19937 rng(0); std::shuffle(loadedDocuments.begin(), loadedDocuments.end(), rng); for (const auto& doc : loadedDocuments) { doc->setDataLibrary(nodeLibrary); std::vector elements = mx::findRenderableElements(doc); REQUIRE(elements.size() > 0); std::string message; bool docValid = doc->validate(&message); REQUIRE(docValid == true); context.getShaderGenerator().registerTypeDefs(doc); mx::StringVec sourceCode; mx::ShaderPtr shader = nullptr; shader = context.getShaderGenerator().generate(elements[0]->getName(), elements[0], context); REQUIRE(shader != nullptr); REQUIRE(shader->getSourceCode(mx::Stage::PIXEL).length() > 0); } } void ShaderGeneratorTester::checkImplementationUsage(const mx::StringSet& usedImpls, const mx::GenContext& context, std::ostream& stream) { // Get list of implementations for a given target. std::set targetImpls; const std::vector& children = _dependLib->getChildren(); for (const auto& child : children) { mx::ImplementationPtr impl = child->asA(); if (impl && impl->getTarget() == _shaderGenerator->getTarget()) { targetImpls.insert(impl); } } mx::StringSet whiteList; getImplementationWhiteList(whiteList); unsigned int implementationUseCount = 0; mx::StringVec skippedImplementations; mx::StringVec missedImplementations; for (const auto& targetImpl : targetImpls) { const std::string& implName = targetImpl->getName(); // Skip white-list items bool inWhiteList = false; for (const auto& w : whiteList) { if (implName.find(w) != std::string::npos) { inWhiteList = true; break; } } if (inWhiteList) { skippedImplementations.push_back(implName); implementationUseCount++; continue; } if (usedImpls.count(implName)) { implementationUseCount++; continue; } if (context.findNodeImplementation(implName)) { implementationUseCount++; continue; } missedImplementations.push_back(implName); } size_t count = targetImpls.size(); stream << "Tested: " << implementationUseCount << " out of: " << count << " library implementations." << std::endl; stream << "Skipped: " << skippedImplementations.size() << " implementations." << std::endl; if (skippedImplementations.size()) { for (const auto& implName : skippedImplementations) { stream << "\t" << implName << std::endl; } } stream << "Untested: " << missedImplementations.size() << " implementations." << std::endl; if (missedImplementations.size()) { for (const auto& implName : missedImplementations) { stream << "\t" << implName << std::endl; } CHECK(implementationUseCount == count); } } bool ShaderGeneratorTester::generateCode(mx::GenContext& context, const std::string& shaderName, mx::TypedElementPtr element, std::ostream& log, mx::StringVec testStages, mx::StringVec& sourceCode) { mx::ShaderPtr shader = nullptr; try { shader = context.getShaderGenerator().generate(shaderName, element, context); } catch (mx::Exception& e) { log << ">> Code generation failure: " << e.what() << "\n"; WARN(std::string(e.what()) + " in " + shaderName); shader = nullptr; } CHECK(shader); if (!shader) { log << ">> Failed to generate shader for element: " << element->getNamePath() << std::endl; return false; } bool stageFailed = false; for (const auto& stage : testStages) { const std::string& code = shader->getSourceCode(stage); sourceCode.push_back(code); bool noSource = code.empty(); CHECK(!noSource); if (noSource) { log << ">> Failed to generate source code for stage: " << stage << std::endl; stageFailed = true; } } return !stageFailed; } void ShaderGeneratorTester::addColorManagement() { if (!_colorManagementSystem && _shaderGenerator) { const std::string& target = _shaderGenerator->getTarget(); _colorManagementSystem = mx::DefaultColorManagementSystem::create(target); if (!_colorManagementSystem) { _logFile << ">> Failed to create color management system for target: " << target << std::endl; } else { _shaderGenerator->setColorManagementSystem(_colorManagementSystem); _colorManagementSystem->loadLibrary(_dependLib); } } } void ShaderGeneratorTester::addUnitSystem() { if (!_unitSystem && _shaderGenerator) { const std::string target = _shaderGenerator->getTarget(); _unitSystem = mx::UnitSystem::create(target); if (!_unitSystem) { _logFile << ">> Failed to create unit system for target: " << target << std::endl; } else { _shaderGenerator->setUnitSystem(_unitSystem); _unitSystem->loadLibrary(_dependLib); _unitSystem->setUnitConverterRegistry(mx::UnitConverterRegistry::create()); mx::UnitTypeDefPtr distanceTypeDef = _dependLib->getUnitTypeDef("distance"); _unitSystem->getUnitConverterRegistry()->addUnitConverter(distanceTypeDef, mx::LinearUnitConverter::create(distanceTypeDef)); _defaultDistanceUnit = "meter"; mx::UnitTypeDefPtr angleTypeDef = _dependLib->getUnitTypeDef("angle"); _unitSystem->getUnitConverterRegistry()->addUnitConverter(angleTypeDef, mx::LinearUnitConverter::create(angleTypeDef)); } } } void ShaderGeneratorTester::setupDependentLibraries() { _dependLib = mx::createDocument(); // Load the standard libraries. loadLibraries({ "libraries" }, _searchPath, _dependLib, _skipLibraryFiles); } LightIdMap ShaderGeneratorTester::computeLightIdMap(const std::vector& nodes) { std::unordered_map idMap; unsigned int id = 1; for (const auto& node : nodes) { auto nodedef = node->getNodeDef(); if (nodedef) { const std::string& name = nodedef->getName(); if (!idMap.count(name)) { idMap[name] = id++; } } } return idMap; } void ShaderGeneratorTester::findLights(mx::DocumentPtr doc, std::vector& lights) { lights.clear(); for (mx::NodePtr node : doc->getNodes()) { if (node->getType() == mx::LIGHT_SHADER_TYPE_STRING) { lights.push_back(node); } } } void ShaderGeneratorTester::registerLights(mx::DocumentPtr doc, const std::vector& lights, mx::GenContext& context) { // Clear context light user data which is set when bindLightShader() // is called. This is necessary in case the light types have already been // registered. mx::HwShaderGenerator::unbindLightShaders(context); if (!lights.empty()) { // Create a list of unique nodedefs and ids for them _lightIdMap = computeLightIdMap(lights); for (const auto& id : _lightIdMap) { mx::NodeDefPtr nodedef = doc->getNodeDef(id.first); if (nodedef) { mx::HwShaderGenerator::bindLightShader(*nodedef, id.second, context); } } } // Clamp the number of light sources to the number registered unsigned int lightSourceCount = static_cast(lights.size()); context.getOptions().hwMaxActiveLightSources = lightSourceCount; } void ShaderGeneratorTester::validate(const mx::GenOptions& generateOptions, const std::string& optionsFilePath) { // Start logging _logFile.open(_logFilePath); // Check for an option file TestSuiteOptions options; if (!options.readOptions(optionsFilePath)) { _logFile << "Cannot read options file: " << optionsFilePath << ". Skipping test." << std::endl; _logFile.close(); return; } // Test has been turned off so just do nothing. if (!runTest(options)) { _logFile << "Target: " << _targetString << " not set to run. Skipping test." << std::endl; _logFile.close(); return; } options.print(_logFile); // Add files to override the files in the test suite to be examined. mx::StringSet overrideFiles; for (const auto& filterFile : options.overrideFiles) { overrideFiles.insert(filterFile); } // Dependent library setup setupDependentLibraries(); addColorManagement(); addUnitSystem(); // Test suite setup addSkipFiles(); // Generation setup setTestStages(); // Load in all documents to test mx::StringVec errorLog; for (const auto& testRoot : _testRootPaths) { mx::loadDocuments(testRoot, _searchPath, _skipFiles, overrideFiles, _documents, _documentPaths, nullptr, &errorLog); } CHECK(errorLog.empty()); for (const auto& error : errorLog) { _logFile << error << std::endl; } // Scan each document for renderable elements and check code generation // // Map to replace "/" in Element path names with "_". mx::StringMap pathMap; pathMap["/"] = "_"; // Add nodedefs to skip when testing addSkipNodeDefs(); // Create our context mx::GenContext context(_shaderGenerator); context.getOptions() = generateOptions; context.registerSourceCodeSearchPath(_searchPath); // Register shader metadata defined in the libraries. _shaderGenerator->registerShaderMetadata(_dependLib, context); // Define working unit if required if (context.getOptions().targetDistanceUnit.empty()) { context.getOptions().targetDistanceUnit = _defaultDistanceUnit; } // Check if a binding context has been set. bool bindingContextUsed = _userData.count(mx::HW::USER_DATA_BINDING_CONTEXT) > 0; // Map to remove invalid names for files when writing to disk mx::StringMap filenameRemap; filenameRemap[":"] = "_"; size_t documentIndex = 0; for (const auto& doc : _documents) { // Apply optional preprocessing. preprocessDocument(doc); _shaderGenerator->registerShaderMetadata(doc, context); // For each new file clear the implementation cache. // Since the new file might contain implementations with names // colliding with implementations in previous test cases. context.clearNodeImplementations(); // Set user data context.clearUserData(); for (auto it : _userData) { context.pushUserData(it.first, it.second); } // Add in dependent libraries bool importedLibrary = false; try { doc->setDataLibrary(_dependLib); importedLibrary = true; } catch (mx::Exception& e) { _logFile << "Failed to import library into file: " << _documentPaths[documentIndex] << ". Error: " << e.what() << std::endl; CHECK(importedLibrary); continue; } // Register typedefs from the document. _shaderGenerator->registerTypeDefs(doc); // Find and register lights findLights(_dependLib, _lights); registerLights(_dependLib, _lights, context); // Find elements to render in the document std::vector elements; try { elements = mx::findRenderableElements(doc); } catch (mx::Exception& e) { _logFile << "Renderables search errors: " << e.what() << std::endl; } if (!elements.empty()) { _logFile << "MTLX Filename :" << _documentPaths[documentIndex] << ". Elements tested: " << std::to_string(elements.size()) << std::endl; documentIndex++; } // Perform document validation std::string message; bool docValid = doc->validate(&message); if (!docValid) { std::string msg = "Document is invalid: [" + doc->getSourceUri() + "] " + message; _logFile << msg; WARN(msg); } CHECK(docValid); // Traverse the renderable elements and run the validation step int missingNodeDefs = 0; int missingImplementations = 0; int codeGenerationFailures = 0; for (const auto& element : elements) { const std::string namePath(element->getNamePath()); mx::OutputPtr output = element->asA(); mx::NodePtr outputNode = element->asA(); if (output) { outputNode = output->getConnectedNode(); } mx::NodeDefPtr nodeDef = outputNode->getNodeDef(); if (nodeDef) { // Allow to skip nodedefs to test if specified const std::string nodeDefName = nodeDef->getName(); if (_skipNodeDefs.count(nodeDefName)) { _logFile << ">> Skipped testing nodedef: " << nodeDefName << std::endl; continue; } mx::string elementName = mx::replaceSubstrings(namePath, pathMap); elementName = mx::createValidName(elementName); elementName = mx::replaceSubstrings(elementName, filenameRemap); mx::InterfaceElementPtr impl = nodeDef->getImplementation(_shaderGenerator->getTarget()); if (impl) { _logFile << "------------ Run validation with element: " << namePath << "------------" << std::endl; mx::StringVec sourceCode; const bool generatedCode = generateCode(context, elementName, element, _logFile, _testStages, sourceCode); // Record implementations tested if (options.checkImplCount) { context.getNodeImplementationNames(_usedImplementations); mx::NodeGraphPtr nodeGraph = impl->asA(); mx::InterfaceElementPtr nodeGraphImpl = nodeGraph ? nodeGraph->getImplementation() : nullptr; _usedImplementations.insert(nodeGraphImpl ? nodeGraphImpl->getName() : impl->getName()); } if (!generatedCode) { _logFile << ">> Failed to generate code for nodedef: " << nodeDefName << std::endl; codeGenerationFailures++; } else if (_writeShadersToDisk && sourceCode.size()) { const std::string elementNameSuffix(bindingContextUsed ? LAYOUT_SUFFIX : mx::EMPTY_STRING); mx::FilePath path = doc->getSourceUri(); if (!path.isEmpty()) { std::string testFileName = path[path.size() - 1]; size_t pos = testFileName.rfind('.'); if (pos != std::string::npos) testFileName = testFileName.substr(0, pos); path = path.getParentPath() / testFileName; if (!path.exists()) { path.createDirectory(); } } else { mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); path = searchPath.isEmpty() ? mx::FilePath() : searchPath[0]; } std::vector sourceCodePaths; if (sourceCode.size() > 1) { for (size_t i=0; igetTarget())); sourceCodePaths.push_back(filename); std::ofstream file(filename.asString()); _logFile << "Write source code: " << filename.asString() << std::endl; file << sourceCode[i]; file.close(); } } else { path = path / (elementName + "." + _shaderGenerator->getTarget() + "." + getFileExtensionForTarget(_shaderGenerator->getTarget()) ); sourceCodePaths.push_back(path); std::ofstream file(path.asString()); _logFile << "Write source code: " << path.asString() << std::endl; std::cout << "Write source code: " << path.asString() << std::endl; file << sourceCode[0]; file.close(); } // Run compile test compileSource(sourceCodePaths); } } else { _logFile << ">> Failed to find implementation for nodedef: " << nodeDefName << std::endl; missingImplementations++; } } else { _logFile << ">> Failed to find nodedef for: " << namePath << std::endl; missingNodeDefs++; } } CHECK(missingNodeDefs == 0); CHECK(missingImplementations == 0); CHECK(codeGenerationFailures == 0); } if (options.checkImplCount) { _logFile << "---------------------------------------------------" << std::endl; checkImplementationUsage(_usedImplementations, context, _logFile); } // End logging if (_logFile.is_open()) { _logFile.close(); } } void TestSuiteOptions::print(std::ostream& output) const { output << "Render Test Options:" << std::endl; output << "\tOverride Files: { "; for (const auto& overrideFile : overrideFiles) { output << overrideFile << " "; } output << "} " << std::endl; output << "\tLight Setup Files: { "; for (const auto& lightFile : lightFiles) { output << lightFile << " "; } output << "} " << std::endl; output << "\tTargets to run: " << std::endl; for (const auto& t : targets) { output << "Target: " << t << std::endl; } output << "\tCheck Implementation Usage Count: " << checkImplCount << std::endl; output << "\tDump Generated Code: " << dumpGeneratedCode << std::endl; output << "\tShader Interfaces: " << shaderInterfaces << std::endl; output << "\tRender Size: " << renderSize[0] << "," << renderSize[1] << std::endl; output << "\tDump uniforms and Attributes " << dumpUniformsAndAttributes << std::endl; output << "\tRender Geometry: " << renderGeometry.asString() << std::endl; output << "\tEnable Direct Lighting: " << enableDirectLighting << std::endl; output << "\tEnable Indirect Lighting: " << enableIndirectLighting << std::endl; output << "\tRadiance IBL File Path " << radianceIBLPath.asString() << std::endl; output << "\tIrradiance IBL File Path: " << irradianceIBLPath.asString() << std::endl; output << "\tExtra library paths: " << extraLibraryPaths.asString() << std::endl; output << "\tRender test paths: " << renderTestPaths.asString() << std::endl; output << "\tEnable Reference Quality: " << enableReferenceQuality << std::endl; } bool TestSuiteOptions::readOptions(const std::string& optionFile) { // These strings should make the input names defined in the // GenShaderUtil::TestSuiteOptions nodedef in test suite file _options.mtlx // const std::string RENDER_TEST_OPTIONS_STRING("TestSuiteOptions"); const std::string OVERRIDE_FILES_STRING("overrideFiles"); const std::string TARGETS_STRING("targets"); const std::string LIGHT_FILES_STRING("lightFiles"); const std::string SHADER_INTERFACES_STRING("shaderInterfaces"); const std::string RENDER_SIZE_STRING("renderSize"); const std::string DUMP_UNIFORMS_AND_ATTRIBUTES_STRING("dumpUniformsAndAttributes"); const std::string CHECK_IMPL_COUNT_STRING("checkImplCount"); const std::string DUMP_GENERATED_CODE_STRING("dumpGeneratedCode"); const std::string RENDER_GEOMETRY_STRING("renderGeometry"); const std::string ENABLE_DIRECT_LIGHTING("enableDirectLighting"); const std::string ENABLE_INDIRECT_LIGHTING("enableIndirectLighting"); const std::string RADIANCE_IBL_PATH_STRING("radianceIBLPath"); const std::string IRRADIANCE_IBL_PATH_STRING("irradianceIBLPath"); const std::string SPHERE_GEOMETRY("sphere.obj"); const std::string EXTRA_LIBRARY_PATHS("extraLibraryPaths"); const std::string RENDER_TEST_PATHS("renderTestPaths"); const std::string ENABLE_REFERENCE_QUALITY("enableReferenceQuality"); overrideFiles.clear(); dumpGeneratedCode = false; renderGeometry = SPHERE_GEOMETRY; enableDirectLighting = true; enableIndirectLighting = true; enableReferenceQuality = false; mx::DocumentPtr doc = mx::createDocument(); try { mx::readFromXmlFile(doc, optionFile, mx::FileSearchPath()); mx::NodeDefPtr optionDefs = doc->getNodeDef(RENDER_TEST_OPTIONS_STRING); if (optionDefs) { for (auto p : optionDefs->getInputs()) { const std::string& name = p->getName(); mx::ValuePtr val = p->getValue(); if (val) { if (name == OVERRIDE_FILES_STRING) { overrideFiles = mx::splitString(p->getValueString(), ","); } else if (name == LIGHT_FILES_STRING) { lightFiles = mx::splitString(p->getValueString(), ","); } else if (name == SHADER_INTERFACES_STRING) { shaderInterfaces = val->asA(); } else if (name == RENDER_SIZE_STRING) { renderSize = val->asA(); } else if (name == DUMP_UNIFORMS_AND_ATTRIBUTES_STRING) { dumpUniformsAndAttributes = val->asA(); } else if (name == TARGETS_STRING) { mx::StringVec list = mx::splitString(p->getValueString(), ","); for (const auto& l : list) { targets.insert(l); } } else if (name == CHECK_IMPL_COUNT_STRING) { checkImplCount = val->asA(); } else if (name == DUMP_GENERATED_CODE_STRING) { dumpGeneratedCode = val->asA(); } else if (name == RENDER_GEOMETRY_STRING) { renderGeometry = p->getValueString(); } else if (name == ENABLE_DIRECT_LIGHTING) { enableDirectLighting = val->asA(); } else if (name == ENABLE_INDIRECT_LIGHTING) { enableIndirectLighting = val->asA(); } else if (name == RADIANCE_IBL_PATH_STRING) { radianceIBLPath = p->getValueString(); } else if (name == IRRADIANCE_IBL_PATH_STRING) { irradianceIBLPath = p->getValueString(); } else if (name == EXTRA_LIBRARY_PATHS) { mx::StringVec list = mx::splitString(p->getValueString(), ","); for (const auto& l : list) { extraLibraryPaths.append(mx::FilePath(l)); } } else if (name == RENDER_TEST_PATHS) { mx::StringVec list = mx::splitString(p->getValueString(), ","); for (const auto& l : list) { renderTestPaths.append(mx::FilePath(l)); } } else if (name == ENABLE_REFERENCE_QUALITY) { enableReferenceQuality = val->asA(); } } } } // Handle direct and indirect lighting toggles. if (!enableDirectLighting) { lightFiles.clear(); } if (!enableIndirectLighting) { radianceIBLPath.assign(mx::EMPTY_STRING); irradianceIBLPath.assign(mx::EMPTY_STRING); } // If there is a filter on the files to run turn off profile checking if (!overrideFiles.empty()) { checkImplCount = false; } return true; } catch (mx::Exception& e) { std::cout << e.what(); } return false; } } // namespace GenShaderUtil