#include "pxr/pxr.h"
#include "pxr/base/arch/fileSystem.h"
#include "pxr/base/arch/library.h"
#include "pxr/base/gf/vec2f.h"
#include "pxr/base/gf/vec3f.h"
#include "pxr/base/gf/vec4f.h"
#include "pxr/base/gf/matrix4d.h"
#include "pxr/base/tf/pathUtils.h"
#include "pxr/base/tf/staticTokens.h"
#include "pxr/base/tf/stringUtils.h"
#include "pxr/base/tf/weakPtr.h"
#include "pxr/base/vt/types.h"
#include "pxr/base/vt/array.h"
#include "pxr/usd/ar/resolver.h"
#include "pxr/usd/ndr/debugCodes.h"
#include "pxr/usd/ndr/nodeDiscoveryResult.h"
#include "pxr/usd/sdf/assetPath.h"
#include "pxr/usd/sdr/shaderMetadataHelpers.h"
#include "pxr/usd/sdr/shaderNode.h"
#include "pxr/usd/sdr/shaderProperty.h"
#include "rmanArgsParser/rmanArgsParser.h"
#include "rmanArgsParser/pugixml/pugixml.hpp"
using namespace pugi;
PXR_NAMESPACE_OPEN_SCOPE
using ShaderMetadataHelpers::CreateStringFromStringVec;
using ShaderMetadataHelpers::IsPropertyAnAssetIdentifier;
using ShaderMetadataHelpers::IsPropertyATerminal;
using ShaderMetadataHelpers::IsTruthy;
using ShaderMetadataHelpers::OptionVecVal;
NDR_REGISTER_PARSER_PLUGIN(RmanArgsParserPlugin)
namespace {
const char* nameStr = "name";
const char* paramStr = "param";
const char* outputStr = "output";
const char* helpStr = "help";
const char* hintdictStr = "hintdict";
const char* hintlistStr = "hintlist";
const char* optionsStr = "options";
const char* valueStr = "value";
const char* tagStr = "tag";
const char* tagsStr = "tags";
const char* pageStr = "page";
const char* primvarsStr = "primvars";
const char* departmentsStr = "departments";
const char* shaderTypeStr = "shaderType";
const char* typeTagStr = "typeTag";
inline bool EQUALS(const char* x, const char* y) {
return strcmp(x, y) == 0;
}
}
TF_DEFINE_PRIVATE_TOKENS(
_tokens,
((discoveryType, "args"))
((sourceType, "RmanCpp"))
);
TF_DEFINE_PRIVATE_TOKENS(
_xmlAttributeNames,
((nameAttr, "name"))
((typeAttr, "type"))
((arraySizeAttr, "arraySize"))
((defaultAttr, "default"))
((inputAttr, "input"))
((tagAttr, "tag"))
((vstructmemberAttr, "vstructmember"))
((sdrDefinitionNameAttr, "sdrDefinitionName"))
);
struct SdrShaderRepresentation
{
SdrShaderRepresentation(const NdrNodeDiscoveryResult& discoveryResult)
: name(discoveryResult.name) {}
std::string name;
std::string helpText;
NdrStringVec primvars;
NdrStringVec departments;
NdrStringVec pages;
NdrPropertyUniquePtrVec properties;
TfToken type = SdrPropertyTypes->Unknown;
};
RmanArgsParserPlugin::RmanArgsParserPlugin()
{
}
RmanArgsParserPlugin::~RmanArgsParserPlugin()
{
}
const NdrTokenVec&
RmanArgsParserPlugin::GetDiscoveryTypes() const
{
static const NdrTokenVec discoveryTypes = {_tokens->discoveryType};
return discoveryTypes;
}
const TfToken&
RmanArgsParserPlugin::GetSourceType() const
{
return _tokens->sourceType;
}
NdrNodeUniquePtr
RmanArgsParserPlugin::Parse(const NdrNodeDiscoveryResult& discoveryResult)
{
xml_document doc;
if (!discoveryResult.resolvedUri.empty()) {
bool localFetchSuccessful = ArGetResolver().FetchToLocalResolvedPath(
discoveryResult.uri,
discoveryResult.resolvedUri
);
if (!localFetchSuccessful) {
TF_WARN("Could not localize the args file at URI [%s] into a local "
"path. An invalid Sdr node definition will be created.",
discoveryResult.uri.c_str());
return NdrParserPlugin::GetInvalidNode(discoveryResult);
}
const xml_parse_result result =
doc.load_file(discoveryResult.resolvedUri.c_str());
if (!result) {
TF_WARN("Could not parse args file at URI [%s] because the file "
"could not be opened or was malformed. An invalid Sdr node "
"definition will be created. (Error: %s)",
discoveryResult.uri.c_str(),
result.description());
return NdrParserPlugin::GetInvalidNode(discoveryResult);
}
} else if (!discoveryResult.sourceCode.empty()) {
const xml_parse_result result =
doc.load_string(discoveryResult.sourceCode.c_str());
if (!result) {
TF_WARN("Could not parse given source code for node with identifier"
"'%s' because it was malformed. An invalid Sdr node "
"definition will be created. (Error: %s)",
discoveryResult.identifier.GetText(),
result.description());
return NdrParserPlugin::GetInvalidNode(discoveryResult);
}
} else {
TF_WARN("Invalid NdrNodeDiscoveryResult with identifier '%s': both "
"resolvedUri and sourceCode fields are empty.",
discoveryResult.identifier.GetText());
return NdrParserPlugin::GetInvalidNode(discoveryResult);
}
xml_node rootElem = doc.first_child();
SdrShaderRepresentation shaderRepresentation(discoveryResult);
_Parse(shaderRepresentation, rootElem, "");
NdrTokenMap metadata = discoveryResult.metadata;
if (!shaderRepresentation.departments.empty()) {
metadata[SdrNodeMetadata->Departments] =
CreateStringFromStringVec(shaderRepresentation.departments);
}
if (!shaderRepresentation.pages.empty()) {
metadata[SdrNodeMetadata->Pages] =
CreateStringFromStringVec(shaderRepresentation.pages);
}
if (!shaderRepresentation.primvars.empty()) {
metadata[SdrNodeMetadata->Primvars] =
CreateStringFromStringVec(shaderRepresentation.primvars);
}
if (!shaderRepresentation.helpText.empty()) {
metadata[SdrNodeMetadata->Help] = shaderRepresentation.helpText;
}
return SdrShaderNodeUniquePtr(
new SdrShaderNode(
discoveryResult.identifier,
discoveryResult.version,
shaderRepresentation.name,
discoveryResult.family,
shaderRepresentation.type,
_tokens->sourceType,
discoveryResult.resolvedUri,
_GetDsoPathFromArgsPath(discoveryResult.resolvedUri),
std::move(shaderRepresentation.properties),
metadata,
discoveryResult.sourceCode)
);
}
std::string
RmanArgsParserPlugin::_GetDsoPathFromArgsPath(const std::string &argsPath)
{
const std::string argsExt(".args");
const std::string dsoExt(ARCH_PLUGIN_SUFFIX);
std::vector<std::string> pathElts = TfStringSplit(TfNormPath(argsPath), "/");
if (pathElts.size() < 3 ||
!TfStringEndsWith(argsPath, argsExt) ||
pathElts[pathElts.size()-2] != "Args"){
TF_WARN("Unexpected path for RenderMan args file: %s - "
"expected a form like /path/to/plugins/Args/somePlugin.args",
argsPath.c_str());
return std::string();
}
std::string pluginFileName = TfStringReplace(pathElts.back(),
argsExt,
dsoExt);
pathElts.pop_back();
pathElts.back() = pluginFileName;
return TfStringJoin(pathElts, ARCH_PATH_SEP);
}
SdrShaderPropertyUniquePtr
RmanArgsParserPlugin::_ParseChildElem(
const SdrShaderRepresentation& shaderRep, bool isOutput,
xml_node childElement, const std::string& parentPage) const
{
NdrTokenMap attributes;
NdrOptionVec options;
NdrStringVec validConnectionTypes;
xml_attribute attribute = childElement.first_attribute();
while (attribute) {
attributes.emplace(
TfToken(attribute.name()),
attribute.value()
);
attribute = attribute.next_attribute();
}
if (!parentPage.empty()) {
attributes.insert({
SdrPropertyMetadata->Page,
parentPage
});
}
xml_node attrChild = childElement.first_child();
while (attrChild) {
if (EQUALS(helpStr, attrChild.name())) {
std::ostringstream helpStream;
attrChild.print(helpStream, "\t",
pugi::format_default | pugi::format_no_escapes);
std::string helpText = TfStringTrim(helpStream.str());
if (TfStringStartsWith(helpText, "<help>")) {
helpText = helpText.substr(6);
}
if (TfStringEndsWith(helpText, "</help>")) {
helpText = helpText.substr(0, helpText.size() - 7);
}
attributes[TfToken(helpStr)] = helpText.c_str();
}
else if (EQUALS(hintdictStr, attrChild.name())) {
xml_attribute nameAttr = attrChild.attribute(nameStr);
if (EQUALS(optionsStr, nameAttr.value())) {
xml_node optChild = attrChild.first_child();
while (optChild) {
const TfToken name(optChild.attribute(nameStr).value());
const TfToken value(optChild.attribute(valueStr).value());
options.emplace_back(std::make_pair(name, value));
optChild = optChild.next_sibling();
}
}
}
else if (EQUALS(hintlistStr, attrChild.name())) {
xml_attribute nameAttr = attrChild.attribute(nameStr);
if (EQUALS(optionsStr, nameAttr.value())) {
xml_node optChild = attrChild.first_child();
while (optChild) {
const TfToken value(optChild.attribute(valueStr).value());
options.emplace_back(std::make_pair(value, TfToken()));
optChild = optChild.next_sibling();
}
}
}
else if (EQUALS(tagsStr, attrChild.name())) {
NdrStringVec connTypes =
_GetAttributeValuesFromChildren(attrChild, "value");
for (const auto& connType : connTypes) {
validConnectionTypes.push_back(connType);
}
}
attrChild = attrChild.next_sibling();
}
bool hasTagAttr = attributes.count(_xmlAttributeNames->tagAttr);
if (validConnectionTypes.size() || hasTagAttr) {
if (hasTagAttr) {
validConnectionTypes.push_back(
attributes.at(_xmlAttributeNames->tagAttr));
}
attributes.emplace(
SdrPropertyMetadata->ValidConnectionTypes,
CreateStringFromStringVec(validConnectionTypes)
);
}
if (attributes.count(SdrPropertyMetadata->Options)) {
NdrOptionVec opts = OptionVecVal(
attributes.at(SdrPropertyMetadata->Options)
);
for (const auto& opt : opts) {
options.push_back(opt);
}
attributes.erase(SdrPropertyMetadata->Options);
}
if (attributes.count(_xmlAttributeNames->typeAttr) == 0) {
if (validConnectionTypes.size() > 0) {
attributes.emplace(
_xmlAttributeNames->typeAttr,
validConnectionTypes[0]
);
}
}
return _CreateProperty(
shaderRep, isOutput, attributes, validConnectionTypes, options
);
}
void
RmanArgsParserPlugin::_Parse(
SdrShaderRepresentation& shaderRep, xml_node parent,
const std::string& parentPage)
{
xml_node childElement = parent.first_child();
while (childElement) {
bool isInput = EQUALS(paramStr, childElement.name());
bool isOutput = EQUALS(outputStr, childElement.name());
if (isInput || isOutput) {
shaderRep.properties.emplace_back(
_ParseChildElem(shaderRep, isOutput, childElement, parentPage)
);
}
else if (EQUALS(pageStr, childElement.name())) {
const std::string pageName(childElement.attribute(nameStr).value());
if (parentPage.empty()) {
_Parse(shaderRep, childElement, pageName);
} else {
_Parse(shaderRep, childElement, parentPage + "." + pageName);
}
}
else if (EQUALS(helpStr, childElement.name())) {
const char* helpText = childElement.child_value();
shaderRep.helpText = helpText;
}
else if (EQUALS(primvarsStr, childElement.name())) {
shaderRep.primvars =
_GetAttributeValuesFromChildren(childElement, "name");
}
else if (EQUALS(departmentsStr, childElement.name())) {
shaderRep.departments =
_GetAttributeValuesFromChildren(childElement, "name");
}
else if (EQUALS(shaderTypeStr, childElement.name())) {
xml_attribute nameAttr = childElement.attribute(nameStr);
if (nameAttr) {
shaderRep.type = TfToken(nameAttr.value());
} else {
xml_node attrChild = childElement.first_child();
if (attrChild && EQUALS(tagStr, attrChild.name())) {
nameAttr = attrChild.attribute(valueStr);
shaderRep.type = TfToken(nameAttr.value());
}
}
}
else if (EQUALS(typeTagStr, childElement.name())) {
xml_node attrChild = childElement.first_child();
if (attrChild && EQUALS(tagStr, attrChild.name())) {
xml_attribute attr = attrChild.attribute(valueStr);
shaderRep.type = TfToken(attr.value());
TF_DEBUG(NDR_PARSING).Msg(
"Deprecated 'typeTag' on shader [%s]",
shaderRep.name.c_str());
}
}
childElement = childElement.next_sibling();
}
}
std::tuple<TfToken, size_t>
RmanArgsParserPlugin::_GetTypeName(
const NdrTokenMap& attributes) const
{
size_t arraySize = _Get(attributes, _xmlAttributeNames->arraySizeAttr, 0);
TfToken typeName =
_Get(attributes, _xmlAttributeNames->typeAttr, TfToken());
if (IsPropertyATerminal(attributes)) {
typeName = SdrPropertyTypes->Terminal;
}
return std::make_tuple(typeName, arraySize);
}
VtValue
RmanArgsParserPlugin::_GetVtValue(
const std::string& stringValue,
TfToken& type,
size_t arraySize,
const NdrTokenMap& metadata) const
{
int isDynamicArray =
IsTruthy(SdrPropertyMetadata->IsDynamicArray, metadata);
bool isArray = (arraySize > 0) || isDynamicArray;
if (type == SdrPropertyTypes->Int) {
if (!isArray) {
return VtValue(atoi(stringValue.c_str()));
} else {
NdrStringVec parts = TfStringTokenize(stringValue, " ,");
int numValues = parts.size();
VtIntArray ints(numValues);
for (int i = 0; i < numValues; ++i) {
ints[i] = atoi(parts[i].c_str());
}
return VtValue::Take(ints);
}
}
else if (type == SdrPropertyTypes->String) {
if (!isArray) {
return VtValue(stringValue);
} else {
VtStringArray array;
std::vector<std::string> tokens =
TfStringTokenize(stringValue, " ,");
array.reserve(tokens.size());
for (const std::string& token : tokens) {
array.push_back(token);
}
return VtValue::Take(array);
}
}
else if (type == SdrPropertyTypes->Float) {
if (!isArray) {
return VtValue(static_cast<float>(atof(stringValue.c_str())));
} else {
NdrStringVec parts = TfStringTokenize(stringValue, " ,");
int numValues = parts.size();
VtFloatArray floats(numValues);
for (int i = 0; i < numValues; ++i) {
floats[i] = static_cast<float>(atof(parts[i].c_str()));
}
return VtValue::Take(floats);
}
}
else if (type == SdrPropertyTypes->Color ||
type == SdrPropertyTypes->Point ||
type == SdrPropertyTypes->Normal ||
type == SdrPropertyTypes->Vector) {
NdrStringVec parts = TfStringTokenize(stringValue, " ,");
if (!isArray) {
if (parts.size() == 3) {
return VtValue(
GfVec3f(atof(parts[0].c_str()),
atof(parts[1].c_str()),
atof(parts[2].c_str()))
);
} else {
TF_DEBUG(NDR_PARSING).Msg(
"float3 default value [%s] has %zd values; should "
"have three.", stringValue.c_str(), parts.size());
return VtValue(GfVec3f(0.0, 0.0, 0.0));
}
} else if (isArray && parts.size() % 3 == 0) {
int numElements = parts.size() / 3;
VtVec3fArray array(numElements);
for (int i = 0; i < numElements; ++i) {
array[i] = GfVec3f(atof(parts[3*i + 0].c_str()),
atof(parts[3*i + 1].c_str()),
atof(parts[3*i + 2].c_str()));
}
return VtValue::Take(array);
}
}
else if (type == SdrPropertyTypes->Matrix) {
NdrStringVec parts = TfStringTokenize(stringValue, " ,");
if (!isArray && parts.size() == 16) {
GfMatrix4d mat;
double* values = mat.GetArray();
for (int i = 0; i < 16; ++i) {
values[i] = atof(parts[i].c_str());
}
return VtValue::Take(mat);
}
}
else if (type == SdrPropertyTypes->Struct ||
type == SdrPropertyTypes->Terminal ||
type == SdrPropertyTypes->Vstruct) {
return VtValue();
}
return VtValue();
}
void
RmanArgsParserPlugin::_OutputDeprecationWarning(
const TfToken& attrName, const SdrShaderRepresentation& shaderRep,
const TfToken& propName) const
{
TF_DEBUG(NDR_PARSING).Msg(
"Deprecated '%s' attribute on shader [%s] on property [%s]",
attrName.GetText(), shaderRep.name.c_str(), propName.GetText());
}
SdrShaderPropertyUniquePtr
RmanArgsParserPlugin::_CreateProperty(
const SdrShaderRepresentation& shaderRep, bool isOutput,
NdrTokenMap& attributes, NdrStringVec& validConnectionTypes,
NdrOptionVec& options) const
{
TfToken propName =
_Get(attributes,
_xmlAttributeNames->nameAttr,
TfToken("NAME UNSPECIFIED"));
TfToken definitionName;
TfToken typeName;
size_t arraySize;
std::tie(typeName, arraySize) = _GetTypeName(attributes);
if (typeName.IsEmpty()) {
typeName = SdrPropertyTypes->Unknown;
TF_DEBUG(NDR_PARSING).Msg(
"Property [%s] doesn't have a valid type. "
"Neither an explicit type nor a validConnectionType was specified.",
propName.GetText());
} else {
if (isOutput) {
_OutputDeprecationWarning(
_xmlAttributeNames->typeAttr, shaderRep, propName);
}
}
if (attributes.count(_xmlAttributeNames->tagAttr)) {
_OutputDeprecationWarning(
_xmlAttributeNames->tagAttr, shaderRep, propName);
attributes.insert({
SdrPropertyMetadata->ValidConnectionTypes,
attributes.at(_xmlAttributeNames->tagAttr)
});
attributes.erase(_xmlAttributeNames->tagAttr);
}
if (attributes.count(_xmlAttributeNames->inputAttr)) {
_OutputDeprecationWarning(
_xmlAttributeNames->inputAttr, shaderRep, propName);
}
if (attributes.count(_xmlAttributeNames->vstructmemberAttr)) {
std::string vstructMember =
attributes.at(_xmlAttributeNames->vstructmemberAttr);
if (!vstructMember.empty()) {
size_t dotPos = vstructMember.find('.');
if (dotPos != std::string::npos) {
attributes.insert({
SdrPropertyMetadata->VstructMemberOf,
vstructMember.substr(0, dotPos)
});
attributes.insert({
SdrPropertyMetadata->VstructMemberName,
vstructMember.substr(dotPos + 1)
});
} else {
TF_DEBUG(NDR_PARSING).Msg(
"Bad virtual structure member in %s.%s:%s",
shaderRep.name.c_str(), propName.GetText(),
vstructMember.c_str());
}
}
}
if (attributes.count(_xmlAttributeNames->sdrDefinitionNameAttr)) {
TfToken definitionName =
TfToken(attributes.at(_xmlAttributeNames->sdrDefinitionNameAttr));
attributes[SdrPropertyMetadata->ImplementationName] = propName;
propName = definitionName;
attributes.erase(_xmlAttributeNames->sdrDefinitionNameAttr);
}
NdrTokenMap hints;
for (const auto& pair : attributes) {
const TfToken attrName = pair.first;
const std::string attrValue = pair.second;
if (std::find(SdrPropertyMetadata->allTokens.begin(),
SdrPropertyMetadata->allTokens.end(),
attrName) != SdrPropertyMetadata->allTokens.end()){
continue;
}
if (std::find(_xmlAttributeNames->allTokens.begin(),
_xmlAttributeNames->allTokens.end(),
attrName) != _xmlAttributeNames->allTokens.end()){
continue;
}
hints.insert({attrName, attrValue});
}
_injectParserMetadata(attributes, typeName);
const VtValue defaultValue =
attributes.count(_xmlAttributeNames->defaultAttr)
? _GetVtValue(attributes.at(_xmlAttributeNames->defaultAttr),
typeName,
arraySize,
attributes)
: VtValue();
return SdrShaderPropertyUniquePtr(
new SdrShaderProperty(
propName,
typeName,
defaultValue,
isOutput,
arraySize,
attributes,
hints,
options
)
);
}
void
RmanArgsParserPlugin::_injectParserMetadata(NdrTokenMap& metadata,
const TfToken& typeName) const
{
if (typeName == SdrPropertyTypes->String) {
if (IsPropertyAnAssetIdentifier(metadata)) {
metadata[SdrPropertyMetadata->IsAssetIdentifier] = "";
}
}
}
NdrStringVec
RmanArgsParserPlugin::_GetAttributeValuesFromChildren(
xml_node parent, const char* attrName) const
{
xml_node child = parent.first_child();
NdrStringVec childAttrValues;
while (child) {
xml_attribute attr = child.attribute(attrName);
childAttrValues.emplace_back(attr.value());
child = child.next_sibling();
}
return childAttrValues;
}
std::string
RmanArgsParserPlugin::_Get(
const NdrTokenMap& map, const TfToken& key, std::string defaultValue) const
{
typename NdrTokenMap::const_iterator it = map.find(key);
if (it != map.end()) {
return it->second;
}
return defaultValue;
}
TfToken
RmanArgsParserPlugin::_Get(
const NdrTokenMap& map, const TfToken& key, TfToken defaultValue) const
{
typename NdrTokenMap::const_iterator it = map.find(key);
if (it != map.end()) {
return TfToken(it->second);
}
return defaultValue;
}
int
RmanArgsParserPlugin::_Get(
const NdrTokenMap& map, const TfToken& key, int defaultValue) const
{
typename NdrTokenMap::const_iterator it = map.find(key);
if (it != map.end()) {
int value = defaultValue;
try {
value = stoi(it->second);
} catch (...) {
TF_DEBUG(NDR_PARSING).Msg(
"Attribute [%s] with string value [%s] "
"couldn't be converted to int.",
key.GetText(), it->second.c_str());
}
return value;
}
return defaultValue;
}
float
RmanArgsParserPlugin::_Get(
const NdrTokenMap& map, const TfToken& key, float defaultValue) const
{
typename NdrTokenMap::const_iterator it = map.find(key);
if (it != map.end()) {
float value = defaultValue;
try {
value = stof(it->second);
} catch (...) {
TF_DEBUG(NDR_PARSING).Msg(
"Attribute [%s] with string value [%s] "
"couldn't be converted to float.",
key.GetText(), it->second.c_str());
}
return value;
}
return defaultValue;
}
PXR_NAMESPACE_CLOSE_SCOPE