Skip to content

Commit

Permalink
MDL workaround for structures with material fields (#1274)
Browse files Browse the repository at this point in the history
The test cases in TestSuite/pbrlib/multioutput/multishaderoutput.mtlx generated invalid structs in MDL. This PR creates a separate function for each struct member, if at least one struct member is a material.
  • Loading branch information
krohmerNV authored and ashwinbhat committed Mar 22, 2023
1 parent e16235e commit 9a65de0
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<materialx version="1.38" xmlns:xi="http://www.w3.org/2001/XInclude">
<nodegraph name="NG_multi_shader_output" nodedef="ND_multi_shader_output">
<burley_diffuse_bsdf name="burley_bsdf" type="BSDF">
<input name="color" type="color3" interfacename="diffuse_color" />
</burley_diffuse_bsdf>
<dielectric_bsdf name="dielectric_bsdf" type="BSDF">
<input name="tint" type="color3" interfacename="diffuse_color" />
</dielectric_bsdf>
<surface name="burley_surface" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="burley_bsdf" />
</surface>
<surface name="dielectric_surface" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="dielectric_bsdf" />
</surface>
<output name="burley_out" type="surfaceshader" nodename="burley_surface" />
<output name="dielectric_out" type="surfaceshader" nodename="dielectric_surface" />
</nodegraph>
<nodedef name="ND_multi_shader_output" node="customtype" version="1.0" isdefaultversion="true">
<input name="diffuse_color" type="color3" value="0.25, 0.50, 0.75" />
<output name="burley_out" type="surfaceshader" />
<output name="dielectric_out" type="surfaceshader" />
</nodedef>
<customtype name="customtype_1" type="multioutput">
<input name="diffuse_color" type="color3" value="0.25, 0.50, 0.75" />
<output name="burley_out" type="surfaceshader" value="" />
<output name="dielectric_out" type="surfaceshader" value="" />
</customtype>
<output name="burley_out2" nodename="customtype_1" output="burley_out" type="surfaceshader" />
<output name="dielectric_out2" nodename="customtype_1" output="dielectric_out" type="surfaceshader" />
</materialx>
10 changes: 9 additions & 1 deletion source/MaterialXGenMdl/MdlShaderGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,15 @@ string MdlShaderGenerator::getUpstreamResult(const ShaderInput* input, GenContex
const ShaderNode* upstreamNode = upstreamOutput->getNode();
if (upstreamNode->numOutputs() > 1)
{
variable = upstreamNode->getName() + "_result.mxp_" + upstreamOutput->getName();
const CompoundNodeMdl* upstreamNodeMdl = dynamic_cast<const CompoundNodeMdl*>(&upstreamNode->getImplementation());
if (upstreamNodeMdl && upstreamNodeMdl->unrollReturnStructMembers())
{
variable = upstreamNode->getName() + "__" + upstreamOutput->getName();
}
else
{
variable = upstreamNode->getName() + "_result.mxp_" + upstreamOutput->getName();
}
}
else
{
Expand Down
68 changes: 65 additions & 3 deletions source/MaterialXGenMdl/Nodes/ClosureCompoundNodeMdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,74 @@ void ClosureCompoundNodeMdl::emitFunctionDefinition(const ShaderNode& node, GenC
{
const ShaderGenerator& shadergen = context.getShaderGenerator();

const bool isMaterialExpr = (_rootGraph->hasClassification(ShaderNode::Classification::CLOSURE) ||
_rootGraph->hasClassification(ShaderNode::Classification::SHADER));

// Emit functions for all child nodes
shadergen.emitFunctionDefinitions(*_rootGraph, context, stage);

// split all fields into separate functions
if (!_returnStruct.empty() && _unrollReturnStructMembers)
{
// make sure the upstream definitions are known
for (const ShaderGraphOutputSocket* outputSocket : _rootGraph->getOutputSockets())
{
if (!outputSocket->getConnection())
continue;

const ShaderNode* upstream = outputSocket->getConnection()->getNode();
const bool isMaterialExpr = (upstream->hasClassification(ShaderNode::Classification::CLOSURE) ||
upstream->hasClassification(ShaderNode::Classification::SHADER));

// since the emit fuctions are const, the field name to generate a function for is passed via context
const std::string& fieldName = outputSocket->getName();
GenUserDataStringPtr fieldNamePtr = std::make_shared<GenUserDataString>(fieldName);
context.pushUserData(CompoundNodeMdl::GEN_USER_DATA_RETURN_STRUCT_FIELD_NAME, fieldNamePtr);

// Emit function signature.
shadergen.emitComment("unrolled structure field: " + _returnStruct + "." + fieldName + " (name=\"" + node.getName() + "\")", stage);
emitFunctionSignature(node, context, stage);

// Special case for material expresions.
if (isMaterialExpr)
{
shadergen.emitLine(" = let", stage, false);
}

// Function body.
shadergen.emitScopeBegin(stage);

// Emit all texturing nodes. These are inputs to the
// closure nodes and need to be emitted first.
shadergen.emitFunctionCalls(*_rootGraph, context, stage, ShaderNode::Classification::TEXTURE);

// Emit function calls for internal closures nodes connected to the graph sockets.
// These will in turn emit function calls for any dependent closure nodes upstream.
if (upstream->getParent() == _rootGraph.get() &&
(upstream->hasClassification(ShaderNode::Classification::CLOSURE) || upstream->hasClassification(ShaderNode::Classification::SHADER)))
{
shadergen.emitFunctionCall(*upstream, context, stage);
}

// Emit final results
if (isMaterialExpr)
{
shadergen.emitScopeEnd(stage);
const string result = shadergen.getUpstreamResult(outputSocket, context);
shadergen.emitLine("in material(" + result + ")", stage);
}
else
{
const string result = shadergen.getUpstreamResult(outputSocket, context);
shadergen.emitLine("return " + result, stage);
}
shadergen.emitLineBreak(stage);

context.popUserData(CompoundNodeMdl::GEN_USER_DATA_RETURN_STRUCT_FIELD_NAME);
}
return;
}

const bool isMaterialExpr = (_rootGraph->hasClassification(ShaderNode::Classification::CLOSURE) ||
_rootGraph->hasClassification(ShaderNode::Classification::SHADER));

// Emit function signature.
emitFunctionSignature(node, context, stage);

Expand Down
87 changes: 78 additions & 9 deletions source/MaterialXGenMdl/Nodes/CompoundNodeMdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

MATERIALX_NAMESPACE_BEGIN

const string CompoundNodeMdl::GEN_USER_DATA_RETURN_STRUCT_FIELD_NAME = "returnStructFieldName";

ShaderNodeImplPtr CompoundNodeMdl::create()
{
return std::make_shared<CompoundNodeMdl>();
Expand All @@ -28,6 +30,15 @@ void CompoundNodeMdl::initialize(const InterfaceElement& element, GenContext& co
{
_returnStruct = _functionName + "__result";
}

// Materials can not be members of structs. Identify this case in order to handle it.
for (const ShaderGraphOutputSocket* output : _rootGraph->getOutputSockets())
{
if (output->getType()->getSemantic() == TypeDesc::SEMANTIC_SHADER)
{
_unrollReturnStructMembers = true;
}
}
}

void CompoundNodeMdl::emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
Expand Down Expand Up @@ -94,10 +105,48 @@ void CompoundNodeMdl::emitFunctionCall(const ShaderNode& node, GenContext& conte
DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
{
const ShaderGenerator& shadergen = context.getShaderGenerator();
const Syntax& syntax = shadergen.getSyntax();

// Begin function call.
if (!_returnStruct.empty())
{
// when unrolling structure members, the call that creates the struct needs to skipped
if (_unrollReturnStructMembers)
{
// make sure the upstream definitions are known
shadergen.emitComment("fill unrolled structure fields: " + _returnStruct + " (name=\"" + node.getName() + "\")", stage);
for (const ShaderGraphOutputSocket* outputSocket : _rootGraph->getOutputSockets())
{
if (!outputSocket->getConnection())
continue;

const std::string& fieldName = outputSocket->getName();

// Emit the struct field.
const string& outputType = syntax.getTypeName(outputSocket->getType());
const string resultVariableName = node.getName() + "__" + fieldName;

shadergen.emitLineBegin(stage);
shadergen.emitString(outputType + " " + resultVariableName + " = ", stage);
shadergen.emitString(_functionName + "__" + fieldName + "(", stage);

// Emit inputs.
string delim = "";
for (ShaderInput* input : node.getInputs())
{
shadergen.emitString(delim, stage);
shadergen.emitInput(input, context, stage);
delim = ", ";
}

// End function call
shadergen.emitString(")", stage);
shadergen.emitLineEnd(stage);
}

return;
}

// Emit the struct multioutput.
const string resultVariableName = node.getName() + "_result";
shadergen.emitLineBegin(stage);
Expand Down Expand Up @@ -135,18 +184,38 @@ void CompoundNodeMdl::emitFunctionSignature(const ShaderNode&, GenContext& conte

if (!_returnStruct.empty())
{
// Define the output struct.
shadergen.emitLine("struct " + _returnStruct, stage, false);
shadergen.emitScopeBegin(stage, Syntax::CURLY_BRACKETS);
for (const ShaderGraphOutputSocket* output : _rootGraph->getOutputSockets())
if (_unrollReturnStructMembers)
{
shadergen.emitLine(syntax.getTypeName(output->getType()) + " mxp_" + output->getName(), stage);
const auto fieldName = context.getUserData<GenUserDataString>(GEN_USER_DATA_RETURN_STRUCT_FIELD_NAME);

if (fieldName)
{
// Begin function signature.
const ShaderGraphOutputSocket* outputSocket = _rootGraph->getOutputSocket(fieldName->getValue());
const string& outputType = syntax.getTypeName(outputSocket->getType());
shadergen.emitLine(outputType + " " + _functionName + "__" + fieldName->getValue(), stage, false);
}
else
{
throw Exception("Error during transformation of struct: " + _returnStruct);
}
}
shadergen.emitScopeEnd(stage, true);
shadergen.emitLineBreak(stage);
else
{

// Begin function signature.
shadergen.emitLine(_returnStruct + " " + _functionName, stage, false);
// Define the output struct.
shadergen.emitLine("struct " + _returnStruct, stage, false);
shadergen.emitScopeBegin(stage, Syntax::CURLY_BRACKETS);
for (const ShaderGraphOutputSocket* output : _rootGraph->getOutputSockets())
{
shadergen.emitLine(syntax.getTypeName(output->getType()) + " mxp_" + output->getName(), stage);
}
shadergen.emitScopeEnd(stage, true);
shadergen.emitLineBreak(stage);

// Begin function signature.
shadergen.emitLine(_returnStruct + " " + _functionName, stage, false);
}
}
else
{
Expand Down
19 changes: 19 additions & 0 deletions source/MaterialXGenMdl/Nodes/CompoundNodeMdl.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@

MATERIALX_NAMESPACE_BEGIN

/// Generator context data class to pass strings.
class GenUserDataString : public GenUserData
{
public:
GenUserDataString(const std::string& value) : _value(value) {}
const string& getValue() const { return _value; }

private:
string _value;
};

/// Shared pointer to a GenUserDataString
using GenUserDataStringPtr = std::shared_ptr<GenUserDataString>;

/// Compound node implementation
class MX_GENMDL_API CompoundNodeMdl : public CompoundNode
{
Expand All @@ -22,10 +36,15 @@ class MX_GENMDL_API CompoundNodeMdl : public CompoundNode
void emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;

bool unrollReturnStructMembers() const { return _unrollReturnStructMembers; }

protected:
void emitFunctionSignature(const ShaderNode& node, GenContext& context, ShaderStage& stage) const;

string _returnStruct;
bool _unrollReturnStructMembers = false;

static const string GEN_USER_DATA_RETURN_STRUCT_FIELD_NAME;
};

MATERIALX_NAMESPACE_END
Expand Down

0 comments on commit 9a65de0

Please sign in to comment.