| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <Atom/RHI.Reflect/SamplerState.h>
- #include <Atom/RPI.Edit/Common/AssetUtils.h>
- #include <Atom/RPI.Edit/Common/JsonUtils.h>
- #include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
- #include <Atom/RPI.Edit/Material/MaterialUtils.h>
- #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
- #include <AtomToolsFramework/Graph/DynamicNode/DynamicNode.h>
- #include <AtomToolsFramework/Graph/DynamicNode/DynamicNodeUtil.h>
- #include <AtomToolsFramework/Graph/GraphTemplateFileDataCacheRequestBus.h>
- #include <AtomToolsFramework/Graph/GraphUtil.h>
- #include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
- #include <AtomToolsFramework/Util/Util.h>
- #include <AzCore/Jobs/Algorithms.h>
- #include <AzCore/Math/Color.h>
- #include <AzCore/Math/Vector2.h>
- #include <AzCore/Math/Vector3.h>
- #include <AzCore/Math/Vector4.h>
- #include <AzCore/RTTI/BehaviorContext.h>
- #include <AzCore/RTTI/RTTI.h>
- #include <AzCore/Serialization/EditContext.h>
- #include <AzCore/Serialization/ObjectStream.h>
- #include <AzCore/Serialization/SerializeContext.h>
- #include <AzCore/Serialization/Utils.h>
- #include <AzCore/Utils/Utils.h>
- #include <AzCore/std/containers/vector.h>
- #include <AzCore/std/sort.h>
- #include <AzCore/std/string/regex.h>
- #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
- #include <Document/MaterialGraphCompiler.h>
- #include <GraphModel/Model/Connection.h>
- namespace MaterialCanvas
- {
- void MaterialGraphCompiler::Reflect(AZ::ReflectContext* context)
- {
- if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
- {
- serialize->Class<MaterialGraphCompiler, AtomToolsFramework::GraphCompiler>()
- ->Version(0)
- ;
- }
- }
- MaterialGraphCompiler::MaterialGraphCompiler(const AZ::Crc32& toolId)
- : AtomToolsFramework::GraphCompiler(toolId)
- {
- }
- MaterialGraphCompiler::~MaterialGraphCompiler()
- {
- }
- AZStd::string MaterialGraphCompiler::GetGraphPath() const
- {
- if (const auto& graphPath = AtomToolsFramework::GraphCompiler::GetGraphPath(); graphPath.ends_with(".materialgraph"))
- {
- return graphPath;
- }
- return AZStd::string::format("%s/Assets/Materials/Generated/untitled.materialgraph", AZ::Utils::GetProjectPath().c_str());
- }
- bool MaterialGraphCompiler::CompileGraph(GraphModel::GraphPtr graph, const AZStd::string& graphName, const AZStd::string& graphPath)
- {
- if (!AtomToolsFramework::GraphCompiler::CompileGraph(graph, graphName, graphPath))
- {
- return false;
- }
- m_includePaths.clear();
- m_classDefinitions.clear();
- m_functionDefinitions.clear();
- m_configIdsVisited.clear();
- m_slotValueTable.clear();
- m_templateNodeCount = 0;
- m_templatePathsForCurrentNode.clear();
- m_templateFileDataVecForCurrentNode.clear();
- m_instructionNodesForCurrentNode.clear();
- BuildSlotValueTable();
- BuildDependencyTables();
- // Traverse all graph nodes and slots searching for settings to generate files from templates
- for (const auto& currentNode : GetAllNodesInExecutionOrder())
- {
- // Search this node for any template path settings that describe files that need to be generated from the graph.
- BuildTemplatePathsForCurrentNode(currentNode);
- // If no template files were specified for this node then skip additional processing and continue to the next one.
- if (m_templatePathsForCurrentNode.empty())
- {
- continue;
- }
- // Attempt to load all of the template files referenced by this node. All of the template data will be tokenized into individual
- // lines and stored in a container so then multiple passes can be made on each file, substituting tokens and filling in
- // details provided by the graph. None of the files generated from this node will be saved until they have all been processed.
- // Template files for material types will be processed in their own pass Because they require special handling and need to be
- // saved before material file templates to not trigger asset processor dependency errors.
- if (!LoadTemplatesForCurrentNode())
- {
- SetState(State::Failed);
- return false;
- }
- // Force delete prior versions of files to be generated if settings are configured to do so.
- DeleteExistingFilesForCurrentNode();
- // Reset the asset processor fingerprint for material and material type files to force them to recompile even if nothing
- // changed. Source material files, generated by material canvas graph templates, never change after they're first generated.
- // Material type and shader source files change constantly based on the configuration of the graph. The AP is not
- // reprocessing or triggering asset notifications for unmodified material assets even though the dependencies are changing.
- // AssetSystemRequestBus::Events::ClearFingerprintForAsset is rarely used but specifically documented to resolve this problem.
- // The consequence is that reflecting some changes in the preview may take more time because all generated assets will be
- // reprocessed including material types where only a material input might have changed.
- ClearFingerprintsForCurrentNode();
- // Perform an initial pass over all template files, injecting include files, class definitions, function definitions, simple
- // things that don't require much processing.
- PreprocessTemplatesForCurrentNode();
- // The next phase injects shader code instructions assembled by traversing the graph from each of the input slots on the current
- // node. The O3DE_GENERATED_INSTRUCTIONS_BEGIN marker will be followed by a list of input slot names corresponding to required
- // variables in the shader. Instructions will only be generated for the current node and nodes connected to the specified
- // inputs. This will allow multiple O3DE_GENERATED_INSTRUCTIONS blocks with different inputs to be specified in multiple
- // locations across multiple files from a single graph.
- // This will also keep track of nodes with instructions and data that contribute to the final shader code. The list of
- // contributing nodes will be used to exclude unused material inputs from generated SRGs and material types.
- BuildInstructionsForCurrentNode(currentNode);
- // At this point, all of the instructions have been generated for all of the template files used by this node. We now also have
- // a complete list of all nodes that contributed instructions to the final shader code across all of the files. Now, we can
- // safely generate the material SRG and material type that only contain variables referenced in the shaders. Without tracking
- // this, all variables would be included in the SRG and material type. The shader compiler would eliminate unused variables from
- // the compiled shader code. The material type would fail to build if it referenced any of the eliminated variables.
- BuildMaterialSrgForCurrentNode();
- // Save all of the generated files except for materials and material types. Generated material type files must be saved after
- // generated shader files to prevent AP errors because of missing dependencies.
- if (!ExportTemplatesMatchingRegex(".*\\.lua\\b") ||
- !ExportTemplatesMatchingRegex(".*\\.azsli\\b") ||
- !ExportTemplatesMatchingRegex(".*\\.azsl\\b") ||
- !ExportTemplatesMatchingRegex(".*\\.shader\\b"))
- {
- SetState(State::Failed);
- return false;
- }
- // Process material type template files, injecting properties from material input nodes.
- if (!BuildMaterialTypeForCurrentNode(currentNode))
- {
- SetState(State::Failed);
- return false;
- }
- // After the material types have been processed and saved, save the materials that reference them.
- if (!ExportTemplatesMatchingRegex(".*\\.material\\b"))
- {
- SetState(State::Failed);
- return false;
- }
- // Increment the template node counter in case we encounter another template node and need to uniquely identify it.
- ++m_templateNodeCount;
- }
- if (!ReportGeneratedFileStatus())
- {
- SetState(State::Failed);
- return false;
- }
- SetState(State::Complete);
- return true;
- }
- void MaterialGraphCompiler::BuildSlotValueTable()
- {
- // Build a table of all values for every slot in the graph.
- m_slotValueTable.clear();
- for (const auto& currentNode : GetAllNodesInExecutionOrder())
- {
- for (const auto& currentSlotPair : currentNode->GetSlots())
- {
- const auto& currentSlot = currentSlotPair.second;
- m_slotValueTable[currentSlot] = currentSlot->GetValue();
- }
- // If this is a dynamic node with slot data type groups, we will search for the largest vector or other data type and convert
- // all of the values in the group to the same type.
- if (auto dynamicNode = azrtti_cast<const AtomToolsFramework::DynamicNode*>(currentNode.get()))
- {
- const auto& nodeConfig = dynamicNode->GetConfig();
- for (const auto& slotDataTypeGroup : nodeConfig.m_slotDataTypeGroups)
- {
- // The slot data group string is separated by vertical bars and can be treated like a regular expression to compare
- // against slot names. The largest vector size is recorded for each slot group.
- const AZStd::regex slotDataTypeGroupRegex(slotDataTypeGroup, AZStd::regex::flag_type::icase);
- // Some nodes might specify a minimum vector size needed to up convert slot values for azsl snippet or output slot
- // requirements. Search all slots in the data type group for the minimum vector size setting to update the default
- // minimum vector size.
- unsigned int vectorSize = 0;
- AtomToolsFramework::VisitDynamicNodeSlotConfigs(
- nodeConfig,
- [&](const AtomToolsFramework::DynamicNodeSlotConfig& slotConfig)
- {
- if (AZStd::regex_match(slotConfig.m_name, slotDataTypeGroupRegex))
- {
- const AZStd::string vectorSizeStr =
- AtomToolsFramework::GetSettingValueByName(slotConfig.m_settings, "materialPropertyMinVectorSize");
- if (!vectorSizeStr.empty())
- {
- vectorSize = AZStd::max(vectorSize, static_cast<unsigned int>(AZStd::stoi(vectorSizeStr)));
- }
- }
- });
- for (const auto& currentSlotPair : currentNode->GetSlots())
- {
- const auto& currentSlot = currentSlotPair.second;
- if (currentSlot->GetSlotDirection() == GraphModel::SlotDirection::Input &&
- AZStd::regex_match(currentSlot->GetName(), slotDataTypeGroupRegex))
- {
- const auto& currentSlotValue = GetValueFromSlotOrConnection(currentSlot);
- vectorSize = AZStd::max(vectorSize, GetVectorSize(currentSlotValue));
- }
- }
- // Once all of the container sizes have been recorded for each slot data group, iterate over all of these slot values
- // and upgrade entries in the map to the bigger type.
- for (const auto& currentSlotPair : currentNode->GetSlots())
- {
- const auto& currentSlot = currentSlotPair.second;
- if (AZStd::regex_match(currentSlot->GetName(), slotDataTypeGroupRegex))
- {
- const auto& currentSlotValue = GetValueFromSlot(currentSlot);
- m_slotValueTable[currentSlot] = ConvertToVector(currentSlotValue, vectorSize);
- }
- }
- }
- }
- }
- }
- void MaterialGraphCompiler::BuildDependencyTables()
- {
- if (!m_graph)
- {
- AZ_Error("MaterialGraphCompiler", false, "Attempting to generate data from invalid graph object.");
- return;
- }
- for (const auto& nodePair : m_graph->GetNodes())
- {
- const auto& currentNode = nodePair.second;
- if (auto dynamicNode = azrtti_cast<const AtomToolsFramework::DynamicNode*>(currentNode.get()))
- {
- if (!m_configIdsVisited.contains(dynamicNode->GetConfig().m_id))
- {
- m_configIdsVisited.insert(dynamicNode->GetConfig().m_id);
- AtomToolsFramework::VisitDynamicNodeSettings(
- dynamicNode->GetConfig(),
- [&](const AtomToolsFramework::DynamicNodeSettingsMap& settings)
- {
- AtomToolsFramework::CollectDynamicNodeSettings(settings, "includePaths", m_includePaths);
- AtomToolsFramework::CollectDynamicNodeSettings(settings, "classDefinitions", m_classDefinitions);
- AtomToolsFramework::CollectDynamicNodeSettings(settings, "functionDefinitions", m_functionDefinitions);
- });
- }
- }
- }
- }
- void MaterialGraphCompiler::BuildTemplatePathsForCurrentNode(const GraphModel::ConstNodePtr& currentNode)
- {
- m_templatePathsForCurrentNode.clear();
- if (auto dynamicNode = azrtti_cast<const AtomToolsFramework::DynamicNode*>(currentNode.get()))
- {
- AtomToolsFramework::VisitDynamicNodeSettings(
- dynamicNode->GetConfig(),
- [&](const AtomToolsFramework::DynamicNodeSettingsMap& settings)
- {
- AtomToolsFramework::CollectDynamicNodeSettings(settings, "templatePaths", m_templatePathsForCurrentNode);
- });
- }
- }
- bool MaterialGraphCompiler::LoadTemplatesForCurrentNode()
- {
- m_templateFileDataVecForCurrentNode.clear();
- for (const auto& templatePath : m_templatePathsForCurrentNode)
- {
- if (!templatePath.ends_with(".materialtype"))
- {
- // Load the unmodified, template source file data, which will be copied and used for insertions, substitutions, and
- // code generation.
- AtomToolsFramework::GraphTemplateFileData templateFileData;
- AtomToolsFramework::GraphTemplateFileDataCacheRequestBus::EventResult(
- templateFileData,
- m_toolId,
- &AtomToolsFramework::GraphTemplateFileDataCacheRequestBus::Events::Load,
- AtomToolsFramework::GetPathWithoutAlias(templatePath));
- if (!templateFileData.IsLoaded())
- {
- m_templateFileDataVecForCurrentNode.clear();
- return false;
- }
- m_templateFileDataVecForCurrentNode.emplace_back(AZStd::move(templateFileData));
- }
- }
- return true;
- }
- void MaterialGraphCompiler::DeleteExistingFilesForCurrentNode()
- {
- if (AtomToolsFramework::GetSettingsValue("/O3DE/Atom/MaterialCanvas/ForceDeleteGeneratedFiles", false))
- {
- AZ::parallel_for_each(
- m_templateFileDataVecForCurrentNode.begin(),
- m_templateFileDataVecForCurrentNode.end(),
- [this](const auto& templateFileData)
- {
- const auto& templateInputPath = AtomToolsFramework::GetPathWithoutAlias(templateFileData.GetPath());
- const auto& templateOutputPath = GetOutputPathFromTemplatePath(templateInputPath);
- auto fileIO = AZ::IO::FileIOBase::GetInstance();
- fileIO->Remove(templateOutputPath.c_str());
- });
- }
- }
- void MaterialGraphCompiler::ClearFingerprintsForCurrentNode()
- {
- if (AtomToolsFramework::GetSettingsValue("/O3DE/Atom/MaterialCanvas/ForceClearAssetFingerprints", false))
- {
- for (const auto& templatePath : m_templatePathsForCurrentNode)
- {
- if (templatePath.ends_with(".material") || templatePath.ends_with(".materialtype"))
- {
- const auto& templateInputPath = AtomToolsFramework::GetPathWithoutAlias(templatePath);
- const auto& templateOutputPath = GetOutputPathFromTemplatePath(templateInputPath);
- AzToolsFramework::AssetSystemRequestBus::Broadcast(
- &AzToolsFramework::AssetSystemRequestBus::Events::ClearFingerprintForAsset, templateOutputPath);
- }
- }
- }
- }
- void MaterialGraphCompiler::PreprocessTemplatesForCurrentNode()
- {
- AZ::parallel_for_each(
- m_templateFileDataVecForCurrentNode.begin(),
- m_templateFileDataVecForCurrentNode.end(),
- [&](auto& templateFileData)
- {
- // Substitute all references to the placeholder graph name with one generated from the document name
- templateFileData.ReplaceSymbol("MaterialGraphName", GetUniqueGraphName());
- // Inject include files found while traversing the graph into any include file blocks in the template.
- templateFileData.ReplaceLinesInBlock(
- "O3DE_GENERATED_INCLUDES_BEGIN",
- "O3DE_GENERATED_INCLUDES_END",
- [&, this]([[maybe_unused]] const AZStd::string& blockHeader)
- {
- // Include file paths will need to be converted to include statements.
- AZStd::vector<AZStd::string> includeStatements;
- includeStatements.reserve(m_includePaths.size());
- for (const auto& path : m_includePaths)
- {
- bool relativePathFound = false;
- AZStd::string relativePath;
- AZStd::string relativePathFolder;
- AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
- relativePathFound,
- &AzToolsFramework::AssetSystem::AssetSystemRequest::GenerateRelativeSourcePath,
- AtomToolsFramework::GetPathWithoutAlias(path),
- relativePath,
- relativePathFolder);
- if (relativePathFound)
- {
- includeStatements.push_back(AZStd::string::format("#include <%s>", relativePath.c_str()));
- }
- }
- return includeStatements;
- });
- // Inject class definitions found while traversing the graph.
- templateFileData.ReplaceLinesInBlock(
- "O3DE_GENERATED_CLASSES_BEGIN",
- "O3DE_GENERATED_CLASSES_END",
- [&]([[maybe_unused]] const AZStd::string& blockHeader)
- {
- return m_classDefinitions;
- });
- // Inject function definitions found while traversing the graph.
- templateFileData.ReplaceLinesInBlock(
- "O3DE_GENERATED_FUNCTIONS_BEGIN",
- "O3DE_GENERATED_FUNCTIONS_END",
- [&]([[maybe_unused]] const AZStd::string& blockHeader)
- {
- return m_functionDefinitions;
- });
- });
- }
- void MaterialGraphCompiler::BuildInstructionsForCurrentNode(const GraphModel::ConstNodePtr& currentNode)
- {
- if (!m_graph)
- {
- AZ_Error("MaterialGraphCompiler", false, "Attempting to generate data from invalid graph object.");
- return;
- }
- m_instructionNodesForCurrentNode.clear();
- m_instructionNodesForCurrentNode.reserve(m_graph->GetNodeCount());
- AZ::parallel_for_each(
- m_templateFileDataVecForCurrentNode.begin(),
- m_templateFileDataVecForCurrentNode.end(),
- [&](auto& templateFileData)
- {
- templateFileData.ReplaceLinesInBlock(
- "O3DE_GENERATED_INSTRUCTIONS_BEGIN",
- "O3DE_GENERATED_INSTRUCTIONS_END",
- [&]([[maybe_unused]] const AZStd::string& blockHeader)
- {
- AZStd::vector<AZStd::string> inputSlotNames;
- AZ::StringFunc::Tokenize(blockHeader, inputSlotNames, ";:, \t\r\n\\/", false, false);
- AZStd::vector<GraphModel::ConstNodePtr> instructionNodesForBlock;
- instructionNodesForBlock.reserve(m_graph->GetNodeCount());
- const auto& lines = GetInstructionsFromConnectedNodes(currentNode, inputSlotNames, instructionNodesForBlock);
- // Adding all of the contributing notes from this blog to the set of all nodes for all blocks.
- AZStd::scoped_lock lock(m_instructionNodesForCurrentNodeMutex);
- m_instructionNodesForCurrentNode.insert(
- m_instructionNodesForCurrentNode.end(), instructionNodesForBlock.begin(), instructionNodesForBlock.end());
- return lines;
- });
- });
- // All of the instruction nodes are gathered in temporary vectors and the results concatenated. The vector needs to be reduced
- // to only contain unique nodes and then resorted by depth.
- AZStd::sort(m_instructionNodesForCurrentNode.begin(), m_instructionNodesForCurrentNode.end());
- m_instructionNodesForCurrentNode.erase(
- AZStd::unique(m_instructionNodesForCurrentNode.begin(), m_instructionNodesForCurrentNode.end()),
- m_instructionNodesForCurrentNode.end());
- AtomToolsFramework::SortNodesInExecutionOrder(m_instructionNodesForCurrentNode);
- }
- void MaterialGraphCompiler::BuildMaterialSrgForCurrentNode()
- {
- AZ::parallel_for_each(
- m_templateFileDataVecForCurrentNode.begin(),
- m_templateFileDataVecForCurrentNode.end(),
- [&](auto& templateFileData)
- {
- templateFileData.ReplaceLinesInBlock(
- "O3DE_GENERATED_MATERIAL_SRG_BEGIN",
- "O3DE_GENERATED_MATERIAL_SRG_END",
- [&]([[maybe_unused]] const AZStd::string& blockHeader)
- {
- return GetMaterialPropertySrgMemberFromNodes(m_instructionNodesForCurrentNode);
- });
- });
- }
- bool MaterialGraphCompiler::BuildMaterialTypeForCurrentNode(const GraphModel::ConstNodePtr& currentNode)
- {
- for (const auto& templatePath : m_templatePathsForCurrentNode)
- {
- if (!templatePath.ends_with(".materialtype"))
- {
- continue;
- }
- // Remove any aliases to resolve the absolute path to the template file
- const auto& templateInputPath = AtomToolsFramework::GetPathWithoutAlias(templatePath);
- const auto& templateOutputPath = GetOutputPathFromTemplatePath(templateInputPath);
- if (!BuildMaterialTypeFromTemplate(currentNode, m_instructionNodesForCurrentNode, templateInputPath, templateOutputPath))
- {
- return false;
- }
- AzFramework::AssetSystemRequestBus::Broadcast(
- &AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, templateOutputPath);
- m_generatedFiles.push_back(templateOutputPath);
- }
- return true;
- }
- bool MaterialGraphCompiler::ExportTemplatesMatchingRegex(const AZStd::string& pattern)
- {
- const AZStd::regex patternRegex(pattern, AZStd::regex::flag_type::icase);
- for (const auto& templateFileData : m_templateFileDataVecForCurrentNode)
- {
- if (AZStd::regex_match(templateFileData.GetPath(), patternRegex))
- {
- const auto& templateOutputPath = GetOutputPathFromTemplatePath(templateFileData.GetPath());
- if (!templateFileData.Save(templateOutputPath))
- {
- return false;
- }
- AzFramework::AssetSystemRequestBus::Broadcast(
- &AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, templateOutputPath);
- m_generatedFiles.push_back(templateOutputPath);
- }
- }
- return true;
- }
- AZStd::string MaterialGraphCompiler::GetOutputPathFromTemplatePath(const AZStd::string& templateInputPath) const
- {
- AZStd::string templateInputFileName;
- AZ::StringFunc::Path::GetFullFileName(templateInputPath.c_str(), templateInputFileName);
- AZStd::string templateOutputPath = GetGraphPath();
- AZ::StringFunc::Path::ReplaceFullName(templateOutputPath, templateInputFileName.c_str());
- AZ::StringFunc::Replace(templateOutputPath, "MaterialGraphName", GetUniqueGraphName().c_str());
- return templateOutputPath;
- }
- unsigned int MaterialGraphCompiler::GetVectorSize(const AZStd::any& slotValue) const
- {
- if (slotValue.is<AZ::Color>())
- {
- return 4;
- }
- if (slotValue.is<AZ::Vector4>())
- {
- return 4;
- }
- if (slotValue.is<AZ::Vector3>())
- {
- return 3;
- }
- if (slotValue.is<AZ::Vector2>())
- {
- return 2;
- }
- if (slotValue.is<bool>() || slotValue.is<int>() || slotValue.is<unsigned int>() || slotValue.is<float>())
- {
- return 1;
- }
- return 0;
- }
- AZStd::any MaterialGraphCompiler::ConvertToScalar(const AZStd::any& slotValue) const
- {
- if (auto v = AZStd::any_cast<const AZ::Color>(&slotValue))
- {
- return AZStd::any(v->GetR());
- }
- if (auto v = AZStd::any_cast<const AZ::Vector4>(&slotValue))
- {
- return AZStd::any(v->GetX());
- }
- if (auto v = AZStd::any_cast<const AZ::Vector3>(&slotValue))
- {
- return AZStd::any(v->GetX());
- }
- if (auto v = AZStd::any_cast<const AZ::Vector2>(&slotValue))
- {
- return AZStd::any(v->GetX());
- }
- return slotValue;
- }
- template<typename T>
- AZStd::any MaterialGraphCompiler::ConvertToVector(const AZStd::any& slotValue) const
- {
- if (auto v = AZStd::any_cast<const AZ::Color>(&slotValue))
- {
- return AZStd::any(T(v->GetAsVector4()));
- }
- if (auto v = AZStd::any_cast<const AZ::Vector4>(&slotValue))
- {
- return AZStd::any(T(*v));
- }
- if (auto v = AZStd::any_cast<const AZ::Vector3>(&slotValue))
- {
- return AZStd::any(T(*v));
- }
- if (auto v = AZStd::any_cast<const AZ::Vector2>(&slotValue))
- {
- return AZStd::any(T(*v));
- }
- return slotValue;
- }
- AZStd::any MaterialGraphCompiler::ConvertToVector(const AZStd::any& slotValue, unsigned int score) const
- {
- switch (score)
- {
- case 4:
- // Skipping color to vector conversions so that they export as the correct type with the material type.
- return slotValue.is<AZ::Color>() ? slotValue : ConvertToVector<AZ::Vector4>(slotValue);
- case 3:
- // Skipping color to vector conversions so that they export as the correct type with the material type.
- return slotValue.is<AZ::Color>() ? slotValue : ConvertToVector<AZ::Vector3>(slotValue);
- case 2:
- return ConvertToVector<AZ::Vector2>(slotValue);
- case 1:
- return ConvertToScalar(slotValue);
- default:
- return slotValue;
- }
- }
- AZStd::any MaterialGraphCompiler::GetValueFromSlot(GraphModel::ConstSlotPtr slot) const
- {
- const auto& slotItr = m_slotValueTable.find(slot);
- return slotItr != m_slotValueTable.end() ? slotItr->second : slot->GetValue();
- }
- AZStd::any MaterialGraphCompiler::GetValueFromSlotOrConnection(GraphModel::ConstSlotPtr slot) const
- {
- for (const auto& connection : slot->GetConnections())
- {
- auto sourceSlot = connection->GetSourceSlot();
- auto targetSlot = connection->GetTargetSlot();
- if (targetSlot == slot)
- {
- return GetValueFromSlotOrConnection(sourceSlot);
- }
- }
- return GetValueFromSlot(slot);
- }
- AZStd::string MaterialGraphCompiler::GetAzslTypeFromSlot(GraphModel::ConstSlotPtr slot) const
- {
- const auto& slotValue = GetValueFromSlot(slot);
- const auto& slotDataType = slot->GetGraphContext()->GetDataTypeForValue(slotValue);
- const auto& slotDataTypeName = slotDataType ? slotDataType->GetDisplayName() : AZStd::string{};
- if (AZ::StringFunc::Equal(slotDataTypeName, "color"))
- {
- return "float4";
- }
- return slotDataTypeName;
- }
- AZStd::string MaterialGraphCompiler::GetAzslValueFromSlot(GraphModel::ConstSlotPtr slot) const
- {
- const auto& slotValue = GetValueFromSlot(slot);
- // This code and some of these rules will be refactored and generalized after splitting this class into a document and builder or
- // compiler class. Once that is done, it will be easier to register types, conversions, substitutions with the system.
- for (const auto& connection : slot->GetConnections())
- {
- auto sourceSlot = connection->GetSourceSlot();
- auto targetSlot = connection->GetTargetSlot();
- if (targetSlot == slot)
- {
- // If there is an incoming connection to this slot, the name of the source slot from the incoming connection will be used as
- // part of the value for the slot. It must be cast to the correct vector type for generated code. These conversions will be
- // extended once the code generator is separated from the document class.
- const auto& sourceSlotValue = GetValueFromSlot(sourceSlot);
- const auto& sourceSlotSymbolName = GetSymbolNameFromSlot(sourceSlot);
- if (slotValue.is<AZ::Vector2>())
- {
- if (sourceSlotValue.is<AZ::Vector3>() ||
- sourceSlotValue.is<AZ::Vector4>() ||
- sourceSlotValue.is<AZ::Color>())
- {
- return AZStd::string::format("(float2)%s", sourceSlotSymbolName.c_str());
- }
- }
- if (slotValue.is<AZ::Vector3>())
- {
- if (sourceSlotValue.is<AZ::Vector2>())
- {
- return AZStd::string::format("float3(%s, 0)", sourceSlotSymbolName.c_str());
- }
- if (sourceSlotValue.is<AZ::Vector4>() ||
- sourceSlotValue.is<AZ::Color>())
- {
- return AZStd::string::format("(float3)%s", sourceSlotSymbolName.c_str());
- }
- }
- if (slotValue.is<AZ::Vector4>() ||
- slotValue.is<AZ::Color>())
- {
- if (sourceSlotValue.is<AZ::Vector2>())
- {
- return AZStd::string::format("float4(%s, 0, 1)", sourceSlotSymbolName.c_str());
- }
- if (sourceSlotValue.is<AZ::Vector3>())
- {
- return AZStd::string::format("float4(%s, 1)", sourceSlotSymbolName.c_str());
- }
- }
- return sourceSlotSymbolName;
- }
- }
- // If the slot's embedded value is being used then generate shader code to represent it. More generic options will be explored to
- // clean this code up, possibly storing numeric values in a two-dimensional floating point array with the layout corresponding to
- // most vector and matrix types.
- if (auto v = AZStd::any_cast<const AZ::Color>(&slotValue))
- {
- return AZStd::string::format("{%g, %g, %g, %g}", v->GetR(), v->GetG(), v->GetB(), v->GetA());
- }
- if (auto v = AZStd::any_cast<const AZ::Vector4>(&slotValue))
- {
- return AZStd::string::format("{%g, %g, %g, %g}", v->GetX(), v->GetY(), v->GetZ(), v->GetW());
- }
- if (auto v = AZStd::any_cast<const AZ::Vector3>(&slotValue))
- {
- return AZStd::string::format("{%g, %g, %g}", v->GetX(), v->GetY(), v->GetZ());
- }
- if (auto v = AZStd::any_cast<const AZ::Vector2>(&slotValue))
- {
- return AZStd::string::format("{%g, %g}", v->GetX(), v->GetY());
- }
- if (auto v = AZStd::any_cast<const AZStd::array<AZ::Vector2, 2>>(&slotValue))
- {
- const auto& value = *v;
- return AZStd::string::format(
- "{%g, %g, %g, %g}",
- value[0].GetX(), value[0].GetY(),
- value[1].GetX(), value[1].GetY());
- }
- if (auto v = AZStd::any_cast<const AZStd::array<AZ::Vector3, 3>>(&slotValue))
- {
- const auto& value = *v;
- return AZStd::string::format(
- "{%g, %g, %g, %g, %g, %g, %g, %g, %g}",
- value[0].GetX(), value[0].GetY(), value[0].GetZ(),
- value[1].GetX(), value[1].GetY(), value[1].GetZ(),
- value[2].GetX(), value[2].GetY(), value[2].GetZ());
- }
- if (auto v = AZStd::any_cast<const AZStd::array<AZ::Vector4, 3>>(&slotValue))
- {
- const auto& value = *v;
- return AZStd::string::format(
- "{%g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g}",
- value[0].GetX(), value[0].GetY(), value[0].GetZ(), value[0].GetW(),
- value[1].GetX(), value[1].GetY(), value[1].GetZ(), value[1].GetW(),
- value[2].GetX(), value[2].GetY(), value[2].GetZ(), value[2].GetW());
- }
- if (auto v = AZStd::any_cast<const AZStd::array<AZ::Vector4, 4>>(&slotValue))
- {
- const auto& value = *v;
- return AZStd::string::format(
- "{%g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g}",
- value[0].GetX(), value[0].GetY(), value[0].GetZ(), value[0].GetW(),
- value[1].GetX(), value[1].GetY(), value[1].GetZ(), value[1].GetW(),
- value[2].GetX(), value[2].GetY(), value[2].GetZ(), value[2].GetW(),
- value[3].GetX(), value[3].GetY(), value[3].GetZ(), value[3].GetW());
- }
- if (auto v = AZStd::any_cast<const float>(&slotValue))
- {
- return AZStd::string::format("%g", *v);
- }
- if (auto v = AZStd::any_cast<const int>(&slotValue))
- {
- return AZStd::string::format("%i", *v);
- }
- if (auto v = AZStd::any_cast<const unsigned int>(&slotValue))
- {
- return AZStd::string::format("%u", *v);
- }
- if (auto v = AZStd::any_cast<const bool>(&slotValue))
- {
- return AZStd::string::format("%u", *v ? 1 : 0);
- }
- if (auto v = AZStd::any_cast<const AZStd::string>(&slotValue))
- {
- return *v;
- }
- return AZStd::string();
- }
- AZStd::string MaterialGraphCompiler::GetAzslSrgMemberFromSlot(
- GraphModel::ConstNodePtr node, const AtomToolsFramework::DynamicNodeSlotConfig& slotConfig) const
- {
- if (const auto& slot = node->GetSlot(slotConfig.m_name))
- {
- const auto& slotValue = GetValueFromSlot(slot);
- if (auto v = AZStd::any_cast<const AZ::RHI::SamplerState>(&slotValue))
- {
- // The fields commented out below either cause errors or are not recognized by the shader compiler.
- AZStd::string srgMember;
- srgMember += AZStd::string::format("Sampler SLOTNAME\n");
- srgMember += AZStd::string::format("{\n");
- srgMember += AZStd::string::format("MaxAnisotropy = %u;\n", AZStd::max<uint32_t>(v->m_anisotropyMax, 1));
- //srgMember += AZStd::string::format("AnisotropyEnable = %u;\n", AZStd::clamp<uint32_t>(v->m_anisotropyEnable, 0, 1);
- srgMember += AZStd::string::format("MinFilter = %s;\n", AZ::RHI::FilterModeNamespace::ToString(v->m_filterMin).data());
- srgMember += AZStd::string::format("MagFilter = %s;\n", AZ::RHI::FilterModeNamespace::ToString(v->m_filterMag).data());
- srgMember += AZStd::string::format("MipFilter = %s;\n", AZ::RHI::FilterModeNamespace::ToString(v->m_filterMip).data());
- srgMember += AZStd::string::format("ReductionType = %s;\n", AZ::RHI::ReductionTypeNamespace::ToString(v->m_reductionType).data());
- //srgMember += AZStd::string::format("ComparisonFunc = %s;\n", AZ::RHI::ComparisonFuncNamespace::ToString(v->m_comparisonFunc).data());
- srgMember += AZStd::string::format("AddressU = %s;\n", AZ::RHI::AddressModeNamespace::ToString(v->m_addressU).data());
- srgMember += AZStd::string::format("AddressV = %s;\n", AZ::RHI::AddressModeNamespace::ToString(v->m_addressV).data());
- srgMember += AZStd::string::format("AddressW = %s;\n", AZ::RHI::AddressModeNamespace::ToString(v->m_addressW).data());
- srgMember += AZStd::string::format("MinLOD = %f;\n", AZStd::max(v->m_mipLodMin, 0.0f));
- srgMember += AZStd::string::format("MaxLOD = %f;\n", AZStd::max(v->m_mipLodMax, 0.0f));
- srgMember += AZStd::string::format("MipLODBias = %f;\n", AZStd::max(v->m_mipLodBias, 0.0f));
- srgMember += AZStd::string::format("BorderColor = %s;\n", AZ::RHI::BorderColorNamespace::ToString(v->m_borderColor).data());
- srgMember += "};\n";
- return srgMember;
- }
- if (AZStd::any_cast<const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(&slotValue))
- {
- return AZStd::string::format("Texture2D SLOTNAME;\n");
- }
- return AZStd::string::format("SLOTTYPE SLOTNAME;\n");
- }
- return AZStd::string();
- }
- AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>> MaterialGraphCompiler::GetSubstitutionSymbolsFromNode(
- GraphModel::ConstNodePtr node) const
- {
- AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>> substitutionSymbols;
- // Reserving space for the number of elements added in this function.
- substitutionSymbols.reserve(node->GetSlots().size() * 4 + 1);
- substitutionSymbols.emplace_back("NODEID", GetSymbolNameFromNode(node));
- for (const auto& slotPair : node->GetSlots())
- {
- const auto& slot = slotPair.second;
- // These substitutions will allow accessing the slot ID, type, value from anywhere in the node's shader code.
- substitutionSymbols.emplace_back(AZStd::string::format("SLOTTYPE\\(%s\\)", slot->GetName().c_str()), GetAzslTypeFromSlot(slot));
- substitutionSymbols.emplace_back(AZStd::string::format("SLOTVALUE\\(%s\\)", slot->GetName().c_str()), GetAzslValueFromSlot(slot));
- substitutionSymbols.emplace_back(AZStd::string::format("SLOTNAME\\(%s\\)", slot->GetName().c_str()), GetSymbolNameFromSlot(slot));
- // This expression will allow direct substitution of node variable names in node configurations with the decorated symbol name.
- // It will match whole words only. No additional decoration should be required on the node configuration side. However, support
- // for the older slot type, name, value substitutions are still supported as a convenience.
- substitutionSymbols.emplace_back(AZStd::string::format("\\b%s\\b", slot->GetName().c_str()), GetSymbolNameFromSlot(slot));
- }
- return substitutionSymbols;
- }
- AZStd::vector<AZStd::string> MaterialGraphCompiler::GetInstructionsFromSlot(
- GraphModel::ConstNodePtr node,
- const AtomToolsFramework::DynamicNodeSlotConfig& slotConfig,
- const AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>>& substitutionSymbols) const
- {
- AZStd::vector<AZStd::string> instructionsForSlot;
- auto slot = node->GetSlot(slotConfig.m_name);
- if (slot && (slot->GetSlotDirection() != GraphModel::SlotDirection::Output || !slot->GetConnections().empty()))
- {
- AtomToolsFramework::CollectDynamicNodeSettings(slotConfig.m_settings, "instructions", instructionsForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer(substitutionSymbols, instructionsForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer("SLOTNAME", GetSymbolNameFromSlot(slot), instructionsForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer("SLOTTYPE", GetAzslTypeFromSlot(slot), instructionsForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer("SLOTVALUE", GetAzslValueFromSlot(slot), instructionsForSlot);
- }
- return instructionsForSlot;
- }
- bool MaterialGraphCompiler::ShouldUseInstructionsFromInputNode(
- GraphModel::ConstNodePtr outputNode, GraphModel::ConstNodePtr inputNode, const AZStd::vector<AZStd::string>& inputSlotNames) const
- {
- if (inputNode == outputNode)
- {
- return true;
- }
- for (const auto& inputSlotName : inputSlotNames)
- {
- if (const auto slot = outputNode->GetSlot(inputSlotName))
- {
- if (slot->GetSlotDirection() == GraphModel::SlotDirection::Input)
- {
- for (const auto& connection : slot->GetConnections())
- {
- AZ_Assert(connection->GetSourceNode() != outputNode, "This should never be the source node on an input connection.");
- AZ_Assert(connection->GetTargetNode() == outputNode, "This should always be the target node on an input connection.");
- if (connection->GetSourceNode() == inputNode || connection->GetSourceNode()->HasInputConnectionFromNode(inputNode))
- {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
- AZStd::vector<GraphModel::ConstNodePtr> MaterialGraphCompiler::GetAllNodesInExecutionOrder() const
- {
- AZStd::vector<GraphModel::ConstNodePtr> nodes;
- if (!m_graph)
- {
- AZ_Error("MaterialGraphCompiler", false, "Attempting to generate data from invalid graph object.");
- return nodes;
- }
- nodes.reserve(m_graph->GetNodes().size());
- for (const auto& nodePair : m_graph->GetNodes())
- {
- nodes.push_back(nodePair.second);
- }
- AtomToolsFramework::SortNodesInExecutionOrder(nodes);
- return nodes;
- }
- AZStd::vector<GraphModel::ConstNodePtr> MaterialGraphCompiler::GetInstructionNodesInExecutionOrder(
- GraphModel::ConstNodePtr outputNode, const AZStd::vector<AZStd::string>& inputSlotNames) const
- {
- AZStd::vector<GraphModel::ConstNodePtr> nodes = GetAllNodesInExecutionOrder();
- AZStd::erase_if(nodes, [this, &outputNode, &inputSlotNames](const auto& node) {
- return !ShouldUseInstructionsFromInputNode(outputNode, node, inputSlotNames);
- });
- return nodes;
- }
- AZStd::vector<AZStd::string> MaterialGraphCompiler::GetInstructionsFromConnectedNodes(
- GraphModel::ConstNodePtr outputNode,
- const AZStd::vector<AZStd::string>& inputSlotNames,
- AZStd::vector<GraphModel::ConstNodePtr>& instructionNodes) const
- {
- AZStd::vector<AZStd::string> instructions;
- for (const auto& inputNode : GetInstructionNodesInExecutionOrder(outputNode, inputSlotNames))
- {
- // Build a list of all nodes that will contribute instructions for the output node
- if (AZStd::find(instructionNodes.begin(), instructionNodes.end(), inputNode) == instructionNodes.end())
- {
- instructionNodes.push_back(inputNode);
- }
- auto dynamicNode = azrtti_cast<const AtomToolsFramework::DynamicNode*>(inputNode.get());
- if (dynamicNode)
- {
- const auto& nodeConfig = dynamicNode->GetConfig();
- const auto& substitutionSymbols = GetSubstitutionSymbolsFromNode(inputNode);
- // Instructions are gathered separately for all of the slot categories because they need to be added in a specific order.
- // Gather and perform substitutions on instructions embedded directly in the node.
- AZStd::vector<AZStd::string> instructionsForNode;
- AtomToolsFramework::CollectDynamicNodeSettings(nodeConfig.m_settings, "instructions", instructionsForNode);
- AtomToolsFramework::ReplaceSymbolsInContainer(substitutionSymbols, instructionsForNode);
- // Gather and perform substitutions on instructions contained in property slots.
- AZStd::vector<AZStd::string> instructionsForPropertySlots;
- for (const auto& slotConfig : nodeConfig.m_propertySlots)
- {
- const auto& instructionsForSlot = GetInstructionsFromSlot(inputNode, slotConfig, substitutionSymbols);
- instructionsForPropertySlots.insert(instructionsForPropertySlots.end(), instructionsForSlot.begin(), instructionsForSlot.end());
- }
- // Gather and perform substitutions on instructions contained in input slots.
- AZStd::vector<AZStd::string> instructionsForInputSlots;
- for (const auto& slotConfig : nodeConfig.m_inputSlots)
- {
- // If this is the output node, only gather instructions for requested input slots.
- if (inputNode == outputNode &&
- AZStd::find(inputSlotNames.begin(), inputSlotNames.end(), slotConfig.m_name) == inputSlotNames.end())
- {
- continue;
- }
- const auto& instructionsForSlot = GetInstructionsFromSlot(inputNode, slotConfig, substitutionSymbols);
- instructionsForInputSlots.insert(instructionsForInputSlots.end(), instructionsForSlot.begin(), instructionsForSlot.end());
- }
- // Gather and perform substitutions on instructions contained in output slots.
- AZStd::vector<AZStd::string> instructionsForOutputSlots;
- for (const auto& slotConfig : nodeConfig.m_outputSlots)
- {
- const auto& instructionsForSlot = GetInstructionsFromSlot(inputNode, slotConfig, substitutionSymbols);
- instructionsForOutputSlots.insert(instructionsForOutputSlots.end(), instructionsForSlot.begin(), instructionsForSlot.end());
- }
- instructions.insert(instructions.end(), instructionsForPropertySlots.begin(), instructionsForPropertySlots.end());
- instructions.insert(instructions.end(), instructionsForInputSlots.begin(), instructionsForInputSlots.end());
- instructions.insert(instructions.end(), instructionsForNode.begin(), instructionsForNode.end());
- instructions.insert(instructions.end(), instructionsForOutputSlots.begin(), instructionsForOutputSlots.end());
- }
- }
- return instructions;
- }
- AZStd::string MaterialGraphCompiler::GetSymbolNameFromNode(GraphModel::ConstNodePtr node) const
- {
- return AtomToolsFramework::GetSymbolNameFromText(AZStd::string::format("node%u_%s", node->GetId(), node->GetTitle()));
- }
- AZStd::string MaterialGraphCompiler::GetSymbolNameFromSlot(GraphModel::ConstSlotPtr slot) const
- {
- bool allowNameSubstitution = true;
- if (auto dynamicNode = azrtti_cast<const AtomToolsFramework::DynamicNode*>(slot->GetParentNode().get()))
- {
- const auto& nodeConfig = dynamicNode->GetConfig();
- AtomToolsFramework::VisitDynamicNodeSlotConfigs(
- nodeConfig,
- [&](const AtomToolsFramework::DynamicNodeSlotConfig& slotConfig)
- {
- if (slot->GetName() == slotConfig.m_name)
- {
- allowNameSubstitution = slotConfig.m_allowNameSubstitution;
- }
- });
- }
- if (!allowNameSubstitution)
- {
- return slot->GetName();
- }
- if (slot->SupportsExtendability())
- {
- return AZStd::string::format(
- "%s_%s_%d", GetSymbolNameFromNode(slot->GetParentNode()).c_str(), slot->GetName().c_str(), slot->GetSlotSubId());
- }
- return AZStd::string::format("%s_%s", GetSymbolNameFromNode(slot->GetParentNode()).c_str(), slot->GetName().c_str());
- }
- AZStd::vector<AZStd::string> MaterialGraphCompiler::GetMaterialPropertySrgMemberFromSlot(
- GraphModel::ConstNodePtr node,
- const AtomToolsFramework::DynamicNodeSlotConfig& slotConfig,
- const AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>>& substitutionSymbols) const
- {
- AZStd::vector<AZStd::string> materialPropertySrgMemberForSlot;
- if (auto slot = node->GetSlot(slotConfig.m_name))
- {
- AtomToolsFramework::CollectDynamicNodeSettings(slotConfig.m_settings, "materialPropertySrgMember", materialPropertySrgMemberForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer(substitutionSymbols, materialPropertySrgMemberForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer("STANDARD_SRG_MEMBER", GetAzslSrgMemberFromSlot(node, slotConfig), materialPropertySrgMemberForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer("SLOTNAME", GetSymbolNameFromSlot(slot), materialPropertySrgMemberForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer("SLOTTYPE", GetAzslTypeFromSlot(slot), materialPropertySrgMemberForSlot);
- AtomToolsFramework::ReplaceSymbolsInContainer("SLOTVALUE", GetAzslValueFromSlot(slot), materialPropertySrgMemberForSlot);
- }
- return materialPropertySrgMemberForSlot;
- }
- AZStd::vector<AZStd::string> MaterialGraphCompiler::GetMaterialPropertySrgMemberFromNodes(
- const AZStd::vector<GraphModel::ConstNodePtr>& instructionNodes) const
- {
- if (!m_graph)
- {
- AZ_Error("MaterialGraphCompiler", false, "Attempting to generate data from invalid graph object.");
- return {};
- }
- AZStd::vector<AZStd::string> materialPropertySrgMember;
- for (const auto& inputNode : instructionNodes)
- {
- auto dynamicNode = azrtti_cast<const AtomToolsFramework::DynamicNode*>(inputNode.get());
- if (dynamicNode)
- {
- const auto& nodeConfig = dynamicNode->GetConfig();
- const auto& substitutionSymbols = GetSubstitutionSymbolsFromNode(inputNode);
- AZStd::vector<AZStd::string> materialPropertySrgMembersForNode;
- AtomToolsFramework::CollectDynamicNodeSettings(
- nodeConfig.m_settings, "materialPropertySrgMember", materialPropertySrgMembersForNode);
- AtomToolsFramework::ReplaceSymbolsInContainer(substitutionSymbols, materialPropertySrgMembersForNode);
- AtomToolsFramework::VisitDynamicNodeSlotConfigs(
- nodeConfig,
- [&](const AtomToolsFramework::DynamicNodeSlotConfig& slotConfig)
- {
- const auto& materialPropertySrgMemberForSlot =
- GetMaterialPropertySrgMemberFromSlot(inputNode, slotConfig, substitutionSymbols);
- materialPropertySrgMembersForNode.insert(
- materialPropertySrgMembersForNode.end(),
- materialPropertySrgMemberForSlot.begin(),
- materialPropertySrgMemberForSlot.end());
- });
- materialPropertySrgMember.insert(
- materialPropertySrgMember.end(), materialPropertySrgMembersForNode.begin(), materialPropertySrgMembersForNode.end());
- }
- }
- return materialPropertySrgMember;
- }
- bool MaterialGraphCompiler::BuildMaterialTypeFromTemplate(
- GraphModel::ConstNodePtr templateNode,
- const AZStd::vector<GraphModel::ConstNodePtr>& instructionNodes,
- const AZStd::string& templateInputPath,
- const AZStd::string& templateOutputPath) const
- {
- using namespace AtomToolsFramework;
- if (!m_graph)
- {
- AZ_Error("MaterialGraphCompiler", false, "Attempting to generate data from invalid graph object.");
- return false;
- }
- if (!templateNode)
- {
- AZ_Error("MaterialGraphCompiler", false, "Attempting to generate data from invalid template node.");
- return false;
- }
- // Load the material type template file, which is the same format as MaterialTypeSourceData with a different extension
- auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(templateInputPath);
- if (!materialTypeOutcome.IsSuccess())
- {
- AZ_Error("MaterialGraphCompiler", false, "Material type template could not be loaded: '%s'.", templateInputPath.c_str());
- return false;
- }
- // Copy the material type source data from the template and begin populating it.
- AZ::RPI::MaterialTypeSourceData materialTypeSourceData = materialTypeOutcome.TakeValue();
- // If the node providing all the template information has a description then assign it to the material type source data.
- materialTypeSourceData.m_description = GetStringValueFromSlot(templateNode->GetSlot("inDescription"));
- // Search the graph for nodes defining material input properties that should be added to the material type and material SRG
- for (const auto& inputNode : instructionNodes)
- {
- // Search for all slots with settings indicating that material type properties should be generated. The settings can correspond
- // to shader inputs, shader options, and other material property values that may or may not have matching entries in the
- // material SRG.
- AZStd::vector<AZStd::pair<GraphModel::ConstSlotPtr, DynamicNodeSlotConfig>> materialPropertyValueSlots;
- if (auto dynamicNode = azrtti_cast<const DynamicNode*>(inputNode.get()))
- {
- VisitDynamicNodeSlotConfigs(
- dynamicNode->GetConfig(),
- [&](const DynamicNodeSlotConfig& slotConfig)
- {
- if (slotConfig.m_settings.contains("materialPropertyName") ||
- slotConfig.m_settings.contains("materialPropertyDisplayName") ||
- slotConfig.m_settings.contains("materialPropertyConnectionType") ||
- slotConfig.m_settings.contains("materialPropertyConnectionName") ||
- slotConfig.m_settings.contains("materialPropertyGroupName") ||
- slotConfig.m_settings.contains("materialPropertyGroup"))
- {
- const auto materialPropertyValueSlot = inputNode->GetSlot(slotConfig.m_name);
- materialPropertyValueSlots.emplace_back(materialPropertyValueSlot, slotConfig);
- }
- });
- }
- // Register all the properties that were parsed out of the slots with the material type.
- for (const auto& [materialPropertyValueSlot, materialPropertyValueSlotConfig] : materialPropertyValueSlots)
- {
- // Sampler states are currently not configurable and will not be added added to the material type, just the material SRG.
- if (!materialPropertyValueSlot || materialPropertyValueSlot->GetValue().empty() ||
- materialPropertyValueSlot->GetValue().is<AZ::RHI::SamplerState>())
- {
- continue;
- }
- const auto& materialPropertyValueSlotSymbolName = GetSymbolNameFromSlot(materialPropertyValueSlot);
- // If the property represents a shader option, the connection name will be defined in a static setting. Otherwise, it will
- // be the slot symbol name which is the same as the variable name added to the SRG and referenced in code.
- const auto& materialPropertyConnectionName = GetFirstNonEmptyString({
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyConnectionName"),
- materialPropertyValueSlotSymbolName
- });
- // The material property connection type determines if the connection represents a shader option, shader input, internal
- // value, or just a placeholder property.
- const auto& materialPropertyConnectionType = GetFirstNonEmptyString({
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyConnectionType")
- });
- // While this might change, material properties representing shader inputs generally have their name, display name,
- // description, and other details spread across multiple, user configurable slots on the same node. Shader options don't
- // need a user configurable name or description because they refer to a predefined option name that will always be used the
- // same way. Several shader options can be exposed on the same node. Because of that, shader options must specify their
- // connection name and copy the name and description directly from the slot instead of having the users enter one.
- const auto& materialPropertyUseSlotConfig = !AZ::StringFunc::Equal(materialPropertyConnectionType, "ShaderInput");
- // The material property name must be unique relative to its group. Material property names are used to read and write
- // property values through the material system API. These will be stored with default values in the material type and
- // overridden values per material. In material canvas, rather than overwhelming the user with Learning and managing the
- // differences between IDs, names, and display names, we will generate the values for symbol and display names Based on a
- // single user specified material input name, slot settings, or the symbol name generated from the node and slot IDs.
- // Find the most appropriate name to use for this property, prioritizing static settings for shader options first.
- const auto& materialPropertyName = GetFirstNonEmptyString({
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyName"),
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyDisplayName"),
- materialPropertyUseSlotConfig ? materialPropertyValueSlotConfig.m_displayName : GetStringValueFromSlot(inputNode->GetSlot("inDisplayName")),
- materialPropertyUseSlotConfig ? materialPropertyValueSlotConfig.m_name : GetStringValueFromSlot(inputNode->GetSlot("inName")),
- materialPropertyValueSlotSymbolName
- });
- // The symbol name used to uniquely identify the property in its group will be generated by transforming the above name to
- // lowercase and replacing all non word characters with underscores.
- const auto& materialPropertySymbolName = GetSymbolNameFromText(materialPropertyName);
- // The display name slot was removed from the original, experimental material output nodes but we are handling it for
- // backwards compatibility. The display name will otherwise be generated by sanitizing and camel casing the property name.
- const auto& materialPropertyDisplayName = GetDisplayNameFromText(GetFirstNonEmptyString({
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyDisplayName"),
- materialPropertyUseSlotConfig ? materialPropertyValueSlotConfig.m_displayName : GetStringValueFromSlot(inputNode->GetSlot("inDisplayName")),
- materialPropertyName
- }));
- if (materialPropertyName.empty() || materialPropertySymbolName.empty() || materialPropertyDisplayName.empty())
- {
- AZ_Error(
- "MaterialGraphCompiler",
- false,
- "Material property name could not be resolved for slot '%s' and template '%s'.",
- materialPropertyValueSlotSymbolName.c_str(),
- templateOutputPath.c_str());
- return false;
- }
- // The group name can be specified in a static setting for shader options or configured for material inputs. Properties that
- // do not explicitly define a group will fall back to the general group.
- const auto& materialPropertyGroupName = GetFirstNonEmptyString({
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyGroup"),
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyGroupName"),
- GetStringValueFromSlot(inputNode->GetSlot("inGroup")),
- "general"
- });
- // Sanitize the symbol and display names for the group to force casing, spacing, and eliminate any potential erroneous input.
- const auto& materialPropertyGroupSymbolName = GetSymbolNameFromText(materialPropertyGroupName);
- const auto& materialPropertyGroupDisplayName = GetDisplayNameFromText(materialPropertyGroupName);
- if (materialPropertyGroupName.empty() || materialPropertyGroupDisplayName.empty())
- {
- AZ_Error(
- "MaterialGraphCompiler",
- false,
- "Material property group could not be resolved for slot '%s' and template '%s'.",
- materialPropertyValueSlotSymbolName.c_str(),
- templateOutputPath.c_str());
- return false;
- }
- // The property description can also be read from static settings for shader options or a user configurable slot
- // for material inputs. If no description is specified, it will fall back to using the material property display name.
- const auto& materialPropertyDescription = GetFirstNonEmptyString({
- GetSettingValueByName(materialPropertyValueSlotConfig.m_settings, "materialPropertyDescription"),
- materialPropertyUseSlotConfig ? materialPropertyValueSlotConfig.m_description : GetStringValueFromSlot(inputNode->GetSlot("inDescription")),
- materialPropertyDisplayName
- });
- // Find or create a property group with the specified name
- auto propertyGroup = materialTypeSourceData.FindPropertyGroup(materialPropertyGroupSymbolName);
- if (!propertyGroup)
- {
- // Add the property group to the material type if it was not already registered
- propertyGroup = materialTypeSourceData.AddPropertyGroup(materialPropertyGroupSymbolName);
- if (!propertyGroup)
- {
- AZ_Error(
- "MaterialGraphCompiler",
- false,
- "Material property group '%s' could not be added for slot '%s' and template '%s'.",
- materialPropertyGroupSymbolName.c_str(),
- materialPropertyValueSlotSymbolName.c_str(),
- templateOutputPath.c_str());
- return false;
- }
- // The unmodified text value will be used as the display name and description for now
- propertyGroup->SetDisplayName(materialPropertyGroupDisplayName);
- propertyGroup->SetDescription(materialPropertyGroupDisplayName);
- }
- // Force material properties to be added with a unique names to prevent collisions that can occur if duplicating
- unsigned int uniqueNameIndex = 0;
- AZStd::string materialPropertySymbolNameUnique = materialPropertySymbolName;
- AZStd::string materialPropertyDisplayNameUnique = materialPropertyDisplayName;
- const auto& existingProperties = propertyGroup->GetProperties();
- while (AZStd::find_if(
- existingProperties.begin(),
- existingProperties.end(),
- [&materialPropertySymbolNameUnique](const auto& existingProperty)
- {
- return materialPropertySymbolNameUnique == existingProperty->GetName();
- }) != existingProperties.end())
- {
- ++uniqueNameIndex;
- materialPropertySymbolNameUnique = AZStd::string::format("%s_%u", materialPropertySymbolName.c_str(), uniqueNameIndex);
- materialPropertyDisplayNameUnique = AZStd::string::format("%s (%u)", materialPropertyDisplayName.c_str(), uniqueNameIndex);
- }
- if (uniqueNameIndex > 0)
- {
- AZ_Warning(
- "MaterialGraphCompiler",
- false,
- "Material property '%s' Was exported with a unique name '%s' in group '%s' for slot '%s' and template '%s'.",
- materialPropertySymbolName.c_str(),
- materialPropertySymbolNameUnique.c_str(),
- materialPropertyGroupSymbolName.c_str(),
- materialPropertyValueSlotSymbolName.c_str(),
- templateOutputPath.c_str());
- }
- auto property = propertyGroup->AddProperty(materialPropertySymbolNameUnique);
- if (!property)
- {
- AZ_Error(
- "MaterialGraphCompiler",
- false,
- "Material property '%s' could not be added to group '%s' for slot '%s' and template '%s'.",
- materialPropertySymbolNameUnique.c_str(),
- materialPropertyGroupSymbolName.c_str(),
- materialPropertyValueSlotSymbolName.c_str(),
- templateOutputPath.c_str());
- return false;
- }
- // Lastly, the property value is read from the slot.
- const auto& materialPropertyValue = GetValueFromSlot(materialPropertyValueSlot);
- // The complete property ID is a combination of the group name and the property name.
- const AZ::Name materialPropertyId(materialPropertyGroupSymbolName + "." + materialPropertySymbolNameUnique);
- property->m_displayName = materialPropertyDisplayNameUnique;
- property->m_description = materialPropertyDescription;
- property->m_enumValues = materialPropertyValueSlotConfig.m_enumValues;
- property->m_value = AZ::RPI::MaterialPropertyValue::FromAny(materialPropertyValue);
- // The property definition requires an explicit type enum that's converted from the actual data type.
- property->m_dataType = GetMaterialPropertyDataTypeFromValue(property->m_value, !property->m_enumValues.empty());
- // Images and enums need additional conversion prior to being saved.
- ConvertToExportFormat(templateOutputPath, materialPropertyId, *property, property->m_value);
- // This property connects to the material SRG member with the same name. Shader options are not yet supported.
- if (!materialPropertyConnectionName.empty())
- {
- if (AZ::StringFunc::Equal(materialPropertyConnectionType, "ShaderInput"))
- {
- property->m_outputConnections.emplace_back(
- AZ::RPI::MaterialPropertyOutputType::ShaderInput, materialPropertyConnectionName);
- }
- else if (AZ::StringFunc::Equal(materialPropertyConnectionType, "ShaderOption"))
- {
- property->m_outputConnections.emplace_back(
- AZ::RPI::MaterialPropertyOutputType::ShaderOption, materialPropertyConnectionName);
- }
- else if (AZ::StringFunc::Equal(materialPropertyConnectionType, "InternalProperty"))
- {
- property->m_outputConnections.emplace_back(
- AZ::RPI::MaterialPropertyOutputType::InternalProperty, materialPropertyConnectionName);
- }
- }
- }
- }
- // Sorting groups and properties in the source data layout to force consistent ordering of the generated material type.
- materialTypeSourceData.SortProperties();
- // The file is written to an in memory buffer before saving to facilitate string substitutions.
- AZStd::string templateOutputText;
- if (!AZ::RPI::JsonUtils::SaveObjectToString(templateOutputText, materialTypeSourceData))
- {
- AZ_Error("MaterialGraphCompiler", false, "Material type template could not be saved: '%s'.", templateOutputPath.c_str());
- return false;
- }
- // Substitute the material graph name and any other Material Canvas specific tokens
- AZ::StringFunc::Replace(templateOutputText, "MaterialGraphName", GetUniqueGraphName().c_str());
- AZ_TracePrintf_IfTrue(
- "MaterialGraphCompiler", IsCompileLoggingEnabled(), "Saving generated file: %s\n", templateOutputPath.c_str());
- // The material type is complete and can be saved to disk.
- const auto writeOutcome = AZ::Utils::WriteFile(templateOutputText, templateOutputPath);
- if (!writeOutcome)
- {
- AZ_Error("MaterialGraphCompiler", false, "Material type template could not be saved: '%s'.", templateOutputPath.c_str());
- return false;
- }
- return true;
- }
- AZStd::string MaterialGraphCompiler::GetUniqueGraphName() const
- {
- return m_templateNodeCount <= 0 ? m_graphName : AZStd::string::format("%s_%03i", m_graphName.c_str(), m_templateNodeCount);
- }
- } // namespace MaterialCanvas
|