Files
2025-05-18 13:04:45 +08:00

1086 lines
40 KiB
C++

//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//
#include <MaterialXTest/External/Catch/catch.hpp>
#include <MaterialXTest/MaterialXGenShader/GenShaderUtil.h>
#include <MaterialXCore/Material.h>
#include <MaterialXCore/Unit.h>
#include <MaterialXFormat/File.h>
#include <MaterialXFormat/Util.h>
#include <MaterialXGenShader/Shader.h>
#include <MaterialXGenShader/Util.h>
#include <MaterialXGenShader/TypeDesc.h>
#include <iostream>
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<std::string, std::string> _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<mx::ImplementationPtr> 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<mx::NodeDefPtr> 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<mx::InterfaceElementPtr> inters = doc->getMatchingImplementations(nodeDefName);
for (const auto& inter2 : inters)
{
mx::ImplementationPtr impl = inter2->asA<mx::Implementation>();
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<mx::Implementation>();
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<mx::NodeGraph>();
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<mx::DocumentPtr> 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<mx::TypedElementPtr> 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<mx::ImplementationPtr> targetImpls;
const std::vector<mx::ElementPtr>& children = _dependLib->getChildren();
for (const auto& child : children)
{
mx::ImplementationPtr impl = child->asA<mx::Implementation>();
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<mx::NodePtr>& nodes)
{
std::unordered_map<std::string, unsigned int> 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<mx::NodePtr>& 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<mx::NodePtr>& 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<unsigned int>(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<mx::TypedElementPtr> 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::Output>();
mx::NodePtr outputNode = element->asA<mx::Node>();
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::NodeGraph>();
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<mx::FilePath> sourceCodePaths;
if (sourceCode.size() > 1)
{
for (size_t i=0; i<sourceCode.size(); ++i)
{
const mx::FilePath filename = path / (elementName + elementNameSuffix + "." + _testStages[i] + "." + getFileExtensionForTarget(_shaderGenerator->getTarget()));
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<int>();
}
else if (name == RENDER_SIZE_STRING)
{
renderSize = val->asA<mx::Vector2>();
}
else if (name == DUMP_UNIFORMS_AND_ATTRIBUTES_STRING)
{
dumpUniformsAndAttributes = val->asA<bool>();
}
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<bool>();
}
else if (name == DUMP_GENERATED_CODE_STRING)
{
dumpGeneratedCode = val->asA<bool>();
}
else if (name == RENDER_GEOMETRY_STRING)
{
renderGeometry = p->getValueString();
}
else if (name == ENABLE_DIRECT_LIGHTING)
{
enableDirectLighting = val->asA<bool>();
}
else if (name == ENABLE_INDIRECT_LIGHTING)
{
enableIndirectLighting = val->asA<bool>();
}
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<bool>();
}
}
}
}
// 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