1 //
2 // Copyright 2018 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #include "pxr/usd/usdShade/shaderDefUtils.h"
25 
26 #include "pxr/usd/usdShade/shader.h"
27 
28 #include "pxr/base/tf/stringUtils.h"
29 
30 #include "pxr/usd/ar/resolver.h"
31 
32 #include "pxr/usd/sdf/assetPath.h"
33 #include "pxr/usd/sdf/types.h"
34 
35 #include "pxr/usd/sdr/shaderMetadataHelpers.h"
36 #include "pxr/usd/sdr/shaderNode.h"
37 #include "pxr/usd/sdr/shaderProperty.h"
38 
39 #include <cctype>
40 
41 PXR_NAMESPACE_OPEN_SCOPE
42 
43 TF_DEFINE_PRIVATE_TOKENS(
44     _tokens,
45 
46     /* property metadata */
47     (primvarProperty)
48     (defaultInput)
49     (implementationName)
50 );
51 
_IsNumber(const std::string & s)52 static bool _IsNumber(const std::string& s)
53 {
54     return !s.empty() &&
55         std::find_if(s.begin(), s.end(),
56                      [](unsigned char c) { return !std::isdigit(c); })
57         == s.end();
58 }
59 
60 /* static */
61 bool
SplitShaderIdentifier(const TfToken & identifier,TfToken * familyName,TfToken * implementationName,NdrVersion * version)62 UsdShadeShaderDefUtils::SplitShaderIdentifier(
63     const TfToken &identifier,
64     TfToken *familyName,
65     TfToken *implementationName,
66     NdrVersion *version)
67 {
68    std::vector<std::string> tokens = TfStringTokenize(identifier.GetString(),
69         "_");
70 
71     if (tokens.empty()) {
72         return false;
73     }
74 
75     *familyName = TfToken(tokens[0]);
76 
77     if (tokens.size() == 1) {
78         *familyName = identifier;
79         *implementationName = identifier;
80         *version = NdrVersion();
81     } else if (tokens.size() == 2) {
82         if (_IsNumber(tokens[tokens.size()-1])) {
83             int major = std::stoi(*tokens.rbegin());
84             *version = NdrVersion(major);
85             *implementationName = *familyName;
86         } else {
87             *version = NdrVersion();
88             *implementationName = identifier;
89         }
90     } else if (tokens.size() > 2) {
91         bool lastTokenIsNumber = _IsNumber(tokens[tokens.size()-1]);
92         bool penultimateTokenIsNumber = _IsNumber(tokens[tokens.size()-2]);
93 
94         if (penultimateTokenIsNumber && !lastTokenIsNumber) {
95             TF_WARN("Invalid shader identifier '%s'.", identifier.GetText());
96             return false;
97         }
98 
99         if (lastTokenIsNumber && penultimateTokenIsNumber) {
100             *version = NdrVersion(std::stoi(tokens[tokens.size()-2]),
101                                   std::stoi(tokens[tokens.size()-1]));
102             *implementationName = TfToken(TfStringJoin(tokens.begin(),
103                 tokens.begin() + (tokens.size() - 2), "_"));
104         } else if (lastTokenIsNumber) {
105             *version = NdrVersion(std::stoi(tokens[tokens.size()-1]));
106             *implementationName  = TfToken(TfStringJoin(tokens.begin(),
107                 tokens.begin() + (tokens.size() - 1), "_"));
108         } else {
109             // No version information is available.
110             *implementationName = identifier;
111             *version = NdrVersion();
112         }
113     }
114 
115     return true;
116 }
117 
118 /* static */
119 NdrNodeDiscoveryResultVec
GetNodeDiscoveryResults(const UsdShadeShader & shaderDef,const std::string & sourceUri)120 UsdShadeShaderDefUtils::GetNodeDiscoveryResults(
121     const UsdShadeShader &shaderDef,
122     const std::string &sourceUri)
123 {
124     NdrNodeDiscoveryResultVec result;
125 
126     // Implementation source must be sourceAsset for the shader to represent
127     // nodes in Sdr.
128     if (shaderDef.GetImplementationSource() != UsdShadeTokens->sourceAsset)
129         return result;
130 
131     const UsdPrim shaderDefPrim = shaderDef.GetPrim();
132     const TfToken &identifier = shaderDefPrim.GetName();
133 
134     // Get the family name, shader name and version information from the
135     // identifier.
136     TfToken family;
137     TfToken name;
138     NdrVersion version;
139     if (!SplitShaderIdentifier(shaderDefPrim.GetName(),
140                 &family, &name, &version)) {
141         // A warning has already been issued by SplitShaderIdentifier.
142         return result;
143     }
144 
145     static const std::string infoNamespace("info:");
146     static const std::string baseSourceAsset(":sourceAsset");
147 
148     // This vector will contain all the info:*:sourceAsset properties.
149     std::vector<UsdProperty> sourceAssetProperties =
150         shaderDefPrim.GetAuthoredProperties(
151         [](const TfToken &propertyName) {
152             const std::string &propertyNameStr = propertyName.GetString();
153             return TfStringStartsWith(propertyNameStr, infoNamespace) &&
154                     TfStringEndsWith(propertyNameStr, baseSourceAsset);
155         });
156 
157     const TfToken discoveryType(ArGetResolver().GetExtension(sourceUri));
158 
159     for (auto &prop : sourceAssetProperties) {
160         UsdAttribute attr = prop.As<UsdAttribute>();
161         if (!attr) {
162             continue;
163         }
164 
165         SdfAssetPath sourceAssetPath;
166         if (attr.Get(&sourceAssetPath) &&
167             !sourceAssetPath.GetAssetPath().empty()) {
168 
169             auto nameTokens =
170                     SdfPath::TokenizeIdentifierAsTokens(attr.GetName());
171             if (nameTokens.size() != 3) {
172                 continue;
173             }
174 
175             const std::string &resolvedUri = sourceAssetPath.GetResolvedPath();
176 
177             // Create a discoveryResult only if the referenced sourceAsset
178             // can be resolved.
179             // XXX: Should we do this regardless and expect the parser to be
180             // able to resolve the unresolved asset path?
181             if (!resolvedUri.empty()) {
182                 const TfToken &sourceType = nameTokens[1];
183 
184                 // Use the prim name as the identifier since it is
185                 // guaranteed to be unique in the file.
186                 // Use the shader id as the name of the shader.
187                 result.emplace_back(
188                     identifier,
189                     version.GetAsDefault(),
190                     name,
191                     family,
192                     discoveryType,
193                     sourceType,
194                     /* uri */ sourceUri,
195                     /* resolvedUri */ sourceUri);
196             } else {
197                 TF_WARN("Unable to resolve info:sourceAsset <%s> with value "
198                     "@%s@.", attr.GetPath().GetText(),
199                     sourceAssetPath.GetAssetPath().c_str());
200             }
201         }
202     }
203 
204     return result;
205 }
206 
207 // Called within _GetShaderPropertyTypeAndArraySize in order to fix up the
208 // default value's type if it was originally a token type because Sdr doesn't
209 // support token types
210 static
211 void
_ConformStringTypeDefaultValue(const SdfValueTypeName & typeName,VtValue * defaultValue)212 _ConformStringTypeDefaultValue(
213     const SdfValueTypeName& typeName,
214     VtValue* defaultValue)
215 {
216     // If the SdrPropertyType would be string but the SdfTypeName is token,
217     // we need to convert the default value's type from token to string so that
218     // there is no inconsistency between the property type and the default value
219     if (defaultValue && !defaultValue->IsEmpty()) {
220         if (typeName == SdfValueTypeNames->Token) {
221             if (defaultValue->IsHolding<TfToken>()) {
222                 const TfToken& tokenVal =
223                     defaultValue->UncheckedGet<TfToken>();
224                 *defaultValue = VtValue(tokenVal.GetString());
225             }
226         } else if (typeName == SdfValueTypeNames->TokenArray) {
227             if (defaultValue->IsHolding< VtArray<TfToken> >()) {
228                 const VtArray<TfToken>& tokenVals =
229                     defaultValue->UncheckedGet< VtArray<TfToken> >();
230                 VtStringArray stringVals;
231                 stringVals.reserve(tokenVals.size());
232                 for (const TfToken& tokenVal : tokenVals) {
233                     stringVals.push_back(tokenVal.GetString());
234                 }
235                 *defaultValue = VtValue::Take(stringVals);
236             }
237         }
238     }
239 }
240 
241 // Called within _GetShaderPropertyTypeAndArraySize in order to fix up the
242 // default value's type if it was originally a bool type because Sdr doesn't
243 // support bool types
244 static
245 void
_ConformIntTypeDefaultValue(const SdfValueTypeName & typeName,VtValue * defaultValue)246 _ConformIntTypeDefaultValue(
247     const SdfValueTypeName& typeName,
248     VtValue* defaultValue)
249 {
250     // If the SdrPropertyType would be int but the SdfTypeName is bool,
251     // we need to convert the default value's type from bool to int so that
252     // there is no inconsistency between the property type and the default value
253     if (defaultValue && !defaultValue->IsEmpty()) {
254         if (typeName == SdfValueTypeNames->Bool) {
255             if (defaultValue->IsHolding<bool>()) {
256                 const bool& boolVal =
257                     defaultValue->UncheckedGet<bool>();
258                 *defaultValue = VtValue(boolVal ? 1 : 0);
259             }
260         } else if (typeName == SdfValueTypeNames->BoolArray) {
261             if (defaultValue->IsHolding< VtArray<bool> >()) {
262                 const VtArray<bool>& boolVals =
263                     defaultValue->UncheckedGet< VtArray<bool> >();
264                 VtIntArray intVals;
265                 intVals.reserve(boolVals.size());
266                 for (const bool& boolVal : boolVals) {
267                     intVals.push_back(boolVal ? 1 : 0);
268                 }
269                 *defaultValue = VtValue::Take(intVals);
270             }
271         }
272     }
273 }
274 
275 // Called within _GetShaderPropertyTypeAndArraySize in order to return the
276 // correct array size as determined by the given default value
277 static
278 size_t
_GetArraySize(VtValue * defaultValue)279 _GetArraySize(VtValue* defaultValue) {
280     if (defaultValue && !defaultValue->IsEmpty()
281         && defaultValue->IsArrayValued()) {
282         return defaultValue->GetArraySize();
283     }
284     return 0;
285 }
286 
287 // This function is called to determine a shader property's type and array size,
288 // and it will also conform the default value to the correct type if needed
289 static
290 std::pair<TfToken, size_t>
_GetShaderPropertyTypeAndArraySize(const SdfValueTypeName & typeName,const NdrTokenMap & metadata,VtValue * defaultValue)291 _GetShaderPropertyTypeAndArraySize(
292     const SdfValueTypeName &typeName,
293     const NdrTokenMap& metadata,
294     VtValue* defaultValue)
295 {
296     // XXX Note that the shaderDefParser does not currently parse 'struct' or
297     //     'vstruct' types.
298     //     Structs are not supported in USD but are allowed as an Sdr property
299     //     type. Vstructs are not parsed at the moment because we have no need
300     //     for them in USD-backed shaders currently.
301 
302     // Determine SdrPropertyType from metadata first, since metadata can
303     // override the type dictated otherwise by the SdfValueTypeName
304     if (ShaderMetadataHelpers::IsPropertyATerminal(metadata)) {
305         return std::make_pair(SdrPropertyTypes->Terminal,
306                               _GetArraySize(defaultValue));
307     }
308 
309     // Determine SdrPropertyType from given SdfValueTypeName
310     if (typeName == SdfValueTypeNames->Int ||
311         typeName == SdfValueTypeNames->IntArray ||
312         typeName == SdfValueTypeNames->Bool ||
313         typeName == SdfValueTypeNames->BoolArray) {
314         _ConformIntTypeDefaultValue(typeName, defaultValue);
315         return std::make_pair(SdrPropertyTypes->Int,
316                               _GetArraySize(defaultValue));
317     } else if (typeName == SdfValueTypeNames->String ||
318                typeName == SdfValueTypeNames->Token ||
319                typeName == SdfValueTypeNames->Asset ||
320                typeName == SdfValueTypeNames->StringArray ||
321                typeName == SdfValueTypeNames->TokenArray ||
322                typeName == SdfValueTypeNames->AssetArray) {
323         _ConformStringTypeDefaultValue(typeName, defaultValue);
324         return std::make_pair(SdrPropertyTypes->String,
325                               _GetArraySize(defaultValue));
326     } else if (typeName == SdfValueTypeNames->Float ||
327                typeName == SdfValueTypeNames->FloatArray) {
328         return std::make_pair(SdrPropertyTypes->Float,
329                               _GetArraySize(defaultValue));
330     } else if (typeName == SdfValueTypeNames->Float2 ||
331                typeName == SdfValueTypeNames->Float2Array) {
332         return std::make_pair(SdrPropertyTypes->Float, 2);
333     } else if (typeName == SdfValueTypeNames->Float3 ||
334                typeName == SdfValueTypeNames->Float3Array) {
335         return std::make_pair(SdrPropertyTypes->Float, 3);
336     } else if (typeName == SdfValueTypeNames->Float4 ||
337                typeName == SdfValueTypeNames->Float4Array) {
338         return std::make_pair(SdrPropertyTypes->Float, 4);
339     } else if (typeName == SdfValueTypeNames->Color3f ||
340                typeName == SdfValueTypeNames->Color3fArray) {
341         return std::make_pair(SdrPropertyTypes->Color,
342                               _GetArraySize(defaultValue));
343     } else if (typeName == SdfValueTypeNames->Point3f ||
344                typeName == SdfValueTypeNames->Point3fArray) {
345         return std::make_pair(SdrPropertyTypes->Point,
346                               _GetArraySize(defaultValue));
347     } else if (typeName == SdfValueTypeNames->Vector3f ||
348                typeName == SdfValueTypeNames->Vector3fArray) {
349         return std::make_pair(SdrPropertyTypes->Vector,
350                               _GetArraySize(defaultValue));
351     } else if (typeName == SdfValueTypeNames->Normal3f||
352                typeName == SdfValueTypeNames->Normal3fArray) {
353         return std::make_pair(SdrPropertyTypes->Normal,
354                               _GetArraySize(defaultValue));
355     } else if (typeName == SdfValueTypeNames->Matrix4d ||
356                typeName == SdfValueTypeNames->Matrix4dArray) {
357         return std::make_pair(SdrPropertyTypes->Matrix,
358                               _GetArraySize(defaultValue));
359     } else {
360         TF_RUNTIME_ERROR("Shader property has unsupported type '%s'",
361             typeName.GetAsToken().GetText());
362         return std::make_pair(SdrPropertyTypes->Unknown, 0);
363     }
364 }
365 
366 template <class ShaderProperty>
367 static
368 SdrShaderPropertyUniquePtr
_CreateSdrShaderProperty(const ShaderProperty & shaderProperty,bool isOutput,const VtValue & shaderDefaultValue,const NdrTokenMap & shaderMetadata)369 _CreateSdrShaderProperty(
370     const ShaderProperty& shaderProperty,
371     bool isOutput,
372     const VtValue& shaderDefaultValue,
373     const NdrTokenMap& shaderMetadata)
374 {
375     const std::string propName = shaderProperty.GetBaseName();
376     VtValue defaultValue = shaderDefaultValue;
377     NdrTokenMap metadata = shaderMetadata;
378     NdrTokenMap hints;
379     NdrOptionVec options;
380 
381     // Update metadata if string should represent a SdfAssetPath
382     if (shaderProperty.GetTypeName() == SdfValueTypeNames->Asset ||
383         shaderProperty.GetTypeName() == SdfValueTypeNames->AssetArray) {
384         metadata[SdrPropertyMetadata->IsAssetIdentifier] = "1";
385     }
386 
387     TfToken propertyType;
388     size_t arraySize;
389     std::tie(propertyType, arraySize) = _GetShaderPropertyTypeAndArraySize(
390         shaderProperty.GetTypeName(), shaderMetadata, &defaultValue);
391 
392     return SdrShaderPropertyUniquePtr(new SdrShaderProperty(
393             shaderProperty.GetBaseName(),
394             propertyType,
395             defaultValue,
396             isOutput,
397             arraySize,
398             metadata, hints, options));
399 }
400 
401 /*static*/
402 NdrPropertyUniquePtrVec
GetShaderProperties(const UsdShadeConnectableAPI & shaderDef)403 UsdShadeShaderDefUtils::GetShaderProperties(
404     const UsdShadeConnectableAPI &shaderDef)
405 {
406     NdrPropertyUniquePtrVec result;
407     for (auto &shaderInput : shaderDef.GetInputs(/* onlyAuthored */ false)) {
408         // Only inputs will have default value provided
409         VtValue defaultValue;
410         shaderInput.Get(&defaultValue);
411 
412         NdrTokenMap metadata = shaderInput.GetSdrMetadata();
413 
414         // Only inputs might have this metadata key
415         auto iter = metadata.find(_tokens->defaultInput);
416         if (iter != metadata.end()) {
417             metadata[SdrPropertyMetadata->DefaultInput] = "1";
418             metadata.erase(_tokens->defaultInput);
419         }
420 
421         // Only inputs have the GetConnectability method
422         metadata[SdrPropertyMetadata->Connectable] =
423             shaderInput.GetConnectability() == UsdShadeTokens->interfaceOnly ?
424             "0" : "1";
425 
426         auto implementationName = metadata.find(_tokens->implementationName);
427         if (implementationName != metadata.end()){
428             metadata[SdrPropertyMetadata->ImplementationName] =
429                 implementationName->second;
430             metadata.erase(implementationName);
431         }
432 
433         result.emplace_back(
434             _CreateSdrShaderProperty(
435                 /* shaderProperty */ shaderInput,
436                 /* isOutput */ false,
437                 /* shaderDefaultValue */ defaultValue,
438                 /* shaderMetadata */ metadata));
439     }
440 
441     for (auto &shaderOutput : shaderDef.GetOutputs(/* onlyAuthored */ false)) {
442         result.emplace_back(
443             _CreateSdrShaderProperty(
444                 /* shaderProperty */ shaderOutput,
445                 /* isOutput */ true,
446                 /* shaderDefaultValue */ VtValue() ,
447                 /* shaderMetadata */ shaderOutput.GetSdrMetadata()));
448     }
449 
450     return result;
451 }
452 
453 /*static*/
454 std::string
GetPrimvarNamesMetadataString(const NdrTokenMap metadata,const UsdShadeConnectableAPI & shaderDef)455 UsdShadeShaderDefUtils::GetPrimvarNamesMetadataString(
456     const NdrTokenMap metadata,
457     const UsdShadeConnectableAPI &shaderDef)
458 {
459     // If there's an existing value in the definition, we must append to it.
460     std::vector<std::string> primvarNames;
461     if (metadata.count(SdrNodeMetadata->Primvars)) {
462         primvarNames.push_back(metadata.at(SdrNodeMetadata->Primvars));
463     }
464 
465     for (auto &shdInput : shaderDef.GetInputs(/* onlyAuthored */ false)) {
466         if (shdInput.HasSdrMetadataByKey(_tokens->primvarProperty)) {
467             // Check if the input holds a string here and issue a warning if it
468             // doesn't.
469             if (_GetShaderPropertyTypeAndArraySize(
470                     shdInput.GetTypeName(),
471                     shdInput.GetSdrMetadata(),
472                     nullptr).first !=
473                     SdrPropertyTypes->String) {
474                 TF_WARN("Shader input <%s> is tagged as a primvarProperty, "
475                     "but isn't string-valued.",
476                     shdInput.GetAttr().GetPath().GetText());
477             }
478 
479             primvarNames.push_back("$" + shdInput.GetBaseName().GetString());
480         }
481     }
482 
483     return TfStringJoin(primvarNames, "|");
484 }
485 
486 
487 PXR_NAMESPACE_CLOSE_SCOPE
488 
489