Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

usdShade: Add ShaderValidator #3398

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pxr/usd/usdShade/plugInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@
},
"MaterialBindingRelationships": {
"doc": "All properties named 'material:binding' or in that namespace should be relationships."
},
},
"ShaderValidator": {
"doc": "Shader nodes must have 'id' as the implementationSource, with id values that begin with 'Usd*|ND_*'. Also, shader inputs with connections must each have a single, valid connection source."
},
"ShaderSdrCompliance": {
"doc": "Shader prim's input types must be conforming to their appropriate sdf types in the respective sdr shader.",
"schemaTypes": [
Expand Down
174 changes: 172 additions & 2 deletions pxr/usd/usdShade/testenv/testUsdShadeValidators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "pxr/usd/usd/validationError.h"
#include "pxr/usd/usd/validationRegistry.h"
#include "pxr/usd/usd/validator.h"
#include "pxr/usd/usdGeom/cube.h"
#include "pxr/usd/usdGeom/validatorTokens.h"
#include "pxr/usd/usdGeom/scope.h"
#include "pxr/usd/usdShade/shader.h"
Expand Down Expand Up @@ -47,7 +48,8 @@ TestUsdShadeValidators()
UsdShadeValidatorNameTokens->materialBindingCollectionValidator,
UsdShadeValidatorNameTokens->shaderSdrCompliance,
UsdShadeValidatorNameTokens->subsetMaterialBindFamilyName,
UsdShadeValidatorNameTokens->subsetsMaterialBindFamily
UsdShadeValidatorNameTokens->subsetsMaterialBindFamily,
UsdShadeValidatorNameTokens->shaderValidator,
};

const UsdValidationRegistry& registry =
Expand All @@ -60,7 +62,7 @@ TestUsdShadeValidators()

UsdValidatorMetadataVector metadata =
registry.GetValidatorMetadataForPlugin(_tokens->usdShadePlugin);
TF_AXIOM(metadata.size() == 7);
TF_AXIOM(metadata.size() == 8);
for (const UsdValidatorMetadata& metadata : metadata) {
validatorMetadataNameSet.insert(metadata.name);
}
Expand Down Expand Up @@ -557,6 +559,173 @@ TestUsdShadeEncapsulationRulesValidator()
}
}

void
TestUsdShadeShaderValidator()
{
UsdValidationRegistry &registry = UsdValidationRegistry::GetInstance();
const UsdValidator *validator = registry.GetOrLoadValidatorByName(
UsdShadeValidatorNameTokens->shaderValidator);
TF_AXIOM(validator);

// Create Stage with a shader that will be modified to show all errors
UsdStageRefPtr usdStage = UsdStage::CreateInMemory();

UsdShadeShader testShader =
UsdShadeShader::Define(usdStage, SdfPath("/TestShader"));

// Set Implementation Source to something other than id
// Do not include info:id
testShader.GetImplementationSourceAttr().Set(UsdShadeTokens->sourceAsset);

// Verify appropriate errors occured
UsdValidationErrorVector errors =
validator->Validate(testShader.GetPrim());
TF_AXIOM(errors.size() == 2u);

std::vector<TfToken> expectedErrorIdentifiers = {
TfToken("usdShade:ShaderValidator.NonIdImplementationSource"),
TfToken("usdShade:ShaderValidator.InvalidShaderId")
};
std::vector<std::string> expectedErrorMessages = {
"Shader </TestShader> has non-id implementation source 'sourceAsset'.",
"Shader </TestShader> has unsupported info:id 'None'."
};

for(size_t i = 0; i < errors.size(); ++i)
{
TF_AXIOM(errors[i].GetIdentifier() == expectedErrorIdentifiers[i]);
TF_AXIOM(errors[i].GetType() == UsdValidationErrorType::Error);
TF_AXIOM(errors[i].GetSites().size() == 1u);
TF_AXIOM(errors[i].GetSites()[0].IsValid());
TF_AXIOM(errors[i].GetSites()[0].IsPrim());
TF_AXIOM(errors[i].GetSites()[0].GetPrim().GetPath() ==
SdfPath("/TestShader"));
TF_AXIOM(errors[i].GetMessage() == expectedErrorMessages[i]);
}

// Update implementation source to be id
testShader.GetImplementationSourceAttr().Set(UsdShadeTokens->id);
// Add an invalid info:id value
testShader.GetIdAttr().Set(TfToken("myInvalidId"));

// Verify error occurs
errors = validator->Validate(testShader.GetPrim());
TfToken expectedErrorIdentifier(
"usdShade:ShaderValidator.InvalidShaderId");
TF_AXIOM(errors.size() == 1u);
TF_AXIOM(errors[0].GetIdentifier() == expectedErrorIdentifier);
TF_AXIOM(errors[0].GetType() == UsdValidationErrorType::Error);
TF_AXIOM(errors[0].GetSites().size() == 1u);
TF_AXIOM(errors[0].GetSites()[0].IsValid());
TF_AXIOM(errors[0].GetSites()[0].IsPrim());
TF_AXIOM(errors[0].GetSites()[0].GetPrim().GetPath() ==
SdfPath("/TestShader"));
std::string expectedErrorMsg =
"Shader </TestShader> has unsupported info:id 'myInvalidId'.";
TF_AXIOM(errors[0].GetMessage() == expectedErrorMsg);

// Set a valid info:id value
testShader.GetIdAttr().Set(TfToken("UsdPreviewSurface"));

// Add two more shaders to set up multiple connections
UsdShadeShader sourceShaderOne = UsdShadeShader::Define(
usdStage, SdfPath("/SourceShaderOne"));
UsdShadeShader sourceShaderTwo = UsdShadeShader::Define(
usdStage, SdfPath("/SourceShaderTwo"));
UsdShadeOutput sourceOutputOne = sourceShaderOne.CreateOutput(
TfToken("outValue"), SdfValueTypeNames->Float);
sourceOutputOne.Set(1.0f);
UsdShadeOutput sourceOutputTwo = sourceShaderTwo.CreateOutput(
TfToken("outValue"), SdfValueTypeNames->Float);
sourceOutputTwo.Set(2.0f);

// Create an input for connections
UsdShadeInput shaderInput = testShader.CreateInput(TfToken("myInput"),
SdfValueTypeNames->Float);

// Set multiple connections on the same input
shaderInput.GetAttr().SetConnections({
SdfPath("/SourceShaderOne.outputs:outValue"),
SdfPath("/SourceShaderTwo.outputs:outValue")});

// Verify the error occurs
errors = validator->Validate(testShader.GetPrim());
expectedErrorIdentifier = TfToken(
"usdShade:ShaderValidator.MultipleConnectionSources");
TF_AXIOM(errors.size() == 1u);
TF_AXIOM(errors[0].GetIdentifier() == expectedErrorIdentifier);
TF_AXIOM(errors[0].GetType() == UsdValidationErrorType::Error);
TF_AXIOM(errors[0].GetSites().size() == 1u);
TF_AXIOM(errors[0].GetSites()[0].IsValid());
TF_AXIOM(errors[0].GetSites()[0].IsPrim());
TF_AXIOM(errors[0].GetSites()[0].GetPrim().GetPath() ==
SdfPath("/TestShader"));
expectedErrorMsg =
"Shader input </TestShader.inputs:myInput> has 2 connection "
"sources, but only one is allowed.";

TF_AXIOM(errors[0].GetMessage() == expectedErrorMsg);

// Update the connections to a prim that does not exist
shaderInput.GetAttr().SetConnections({SdfPath("/DoesNotExist")});

// Verify the error occurs
errors = validator->Validate(testShader.GetPrim());
expectedErrorIdentifier = TfToken(
"usdShade:ShaderValidator.MissingConnectionSource");
TF_AXIOM(errors.size() == 1u);
TF_AXIOM(errors[0].GetIdentifier() == expectedErrorIdentifier);
TF_AXIOM(errors[0].GetType() == UsdValidationErrorType::Error);
TF_AXIOM(errors[0].GetSites().size() == 1u);
TF_AXIOM(errors[0].GetSites()[0].IsValid());
TF_AXIOM(errors[0].GetSites()[0].IsPrim());
TF_AXIOM(errors[0].GetSites()[0].GetPrim().GetPath() ==
SdfPath("/TestShader"));
expectedErrorMsg =
"Connection source </DoesNotExist> for shader "
"input </TestShader.inputs:myInput> is missing.";
TF_AXIOM(errors[0].GetMessage() == expectedErrorMsg);

// Create a cube with an output
UsdGeomCube cubePrim = UsdGeomCube::Define(usdStage, SdfPath("/Cube"));
UsdAttribute outValueAttr = cubePrim.GetPrim().CreateAttribute(
TfToken("outputs:outValue"), SdfValueTypeNames->Float, true);

// Set a value for the custom output attribute
outValueAttr.Set(2.0f);

// Update the connection to be something other than a Shader or Material
shaderInput.GetAttr().SetConnections({SdfPath("/Cube.outputs:outValue")});

// Verify the error occurs
errors = validator->Validate(testShader.GetPrim());

expectedErrorIdentifier = TfToken(
"usdShade:ShaderValidator.InvalidConnectionSourcePrimType");
TF_AXIOM(errors.size() == 1u);
TF_AXIOM(errors[0].GetIdentifier() == expectedErrorIdentifier);
TF_AXIOM(errors[0].GetType() == UsdValidationErrorType::Error);
TF_AXIOM(errors[0].GetSites().size() == 1u);
TF_AXIOM(errors[0].GetSites()[0].IsValid());
TF_AXIOM(errors[0].GetSites()[0].IsPrim());
TF_AXIOM(errors[0].GetSites()[0].GetPrim().GetPath() ==
SdfPath("/TestShader"));
expectedErrorMsg =
"Shader input </TestShader.inputs:myInput> has an invalid "
"connection source prim of type 'Cube'.";
TF_AXIOM(errors[0].GetMessage() == expectedErrorMsg);

// Use a valid connection
shaderInput.GetAttr().SetConnections({
SdfPath("/SourceShaderOne.outputs:outValue")
});

errors = validator->Validate(testShader.GetPrim());

// Verify all errors are gone
TF_AXIOM(errors.empty());
}

int
main()
{
Expand All @@ -568,6 +737,7 @@ main()
TestUsdShadeSubsetMaterialBindFamilyName();
TestUsdShadeSubsetsMaterialBindFamily();
TestUsdShadeEncapsulationRulesValidator();
TestUsdShadeShaderValidator();

return EXIT_SUCCESS;
};
37 changes: 22 additions & 15 deletions pxr/usd/usdShade/validatorTokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,34 @@ PXR_NAMESPACE_OPEN_SCOPE
((materialBindingRelationships, "usdShade:MaterialBindingRelationships")) \
((materialBindingCollectionValidator, "usdShade:MaterialBindingCollectionValidator")) \
((shaderSdrCompliance, "usdShade:ShaderSdrCompliance")) \
((shaderValidator, "usdShade:ShaderValidator")) \
((subsetMaterialBindFamilyName, "usdShade:SubsetMaterialBindFamilyName")) \
((subsetsMaterialBindFamily, "usdShade:SubsetsMaterialBindFamily"))

#define USD_SHADE_VALIDATOR_KEYWORD_TOKENS \
(UsdShadeValidators)

#define USD_SHADE_VALIDATION_ERROR_NAME_TOKENS \
((connectableInNonContainer, "ConnectableInNonContainer")) \
((invalidConnectableHierarchy, "InvalidConnectableHierarchy")) \
((missingMaterialBindingAPI, "MissingMaterialBindingAPI")) \
((materialBindingPropNotARel, "MaterialBindingPropNotARel")) \
((invalidMaterialCollection, "InvalidMaterialCollection")) \
((invalidResourcePath, "InvalidResourcePath")) \
((invalidImplSource, "InvalidImplementationSrc")) \
((missingSourceType, "MissingSourceType")) \
((missingShaderIdInRegistry, "MissingShaderIdInRegistry")) \
((missingSourceTypeInRegistry, "MissingSourceTypeInRegistry")) \
((incompatShaderPropertyWarning, "IncompatShaderPropertyWarning")) \
((mismatchPropertyType, "MismatchedPropertyType")) \
((missingFamilyNameOnGeomSubset, "MissingFamilyNameOnGeomSubset")) \
((invalidFamilyType, "InvalidFamilyType")) \
#define USD_SHADE_VALIDATION_ERROR_NAME_TOKENS \
((connectableInNonContainer, "ConnectableInNonContainer")) \
((invalidConnectableHierarchy, "InvalidConnectableHierarchy")) \
((missingMaterialBindingAPI, "MissingMaterialBindingAPI")) \
((materialBindingPropNotARel, "MaterialBindingPropNotARel")) \
((invalidMaterialCollection, "InvalidMaterialCollection")) \
((invalidResourcePath, "InvalidResourcePath")) \
((invalidImplSource, "InvalidImplementationSrc")) \
((missingSourceType, "MissingSourceType")) \
((missingShaderIdInRegistry, "MissingShaderIdInRegistry")) \
((missingSourceTypeInRegistry, "MissingSourceTypeInRegistry")) \
((incompatShaderPropertyWarning, "IncompatShaderPropertyWarning")) \
((mismatchPropertyType, "MismatchedPropertyType")) \
((missingFamilyNameOnGeomSubset, "MissingFamilyNameOnGeomSubset")) \
((invalidFamilyType, "InvalidFamilyType")) \
((nonIdImplementationSource, "NonIdImplementationSource")) \
((invalidShaderId, "InvalidShaderId")) \
((multipleConnectionSources, "MultipleConnectionSources")) \
((missingConnectionSource, "MissingConnectionSource")) \
((invalidConnectionSourcePrimType, "InvalidConnectionSourcePrimType")) \


/// \def USD_SHADE_VALIDATOR_NAME_TOKENS
/// Tokens representing validator names. Note that for plugin provided
Expand Down
Loading