Przeglądaj źródła

Split out MaterialTypeBuilder to be separate from MaterialBuilder.
Common code is now in a new MaterialBuilderUtils.h/cpp, and some is in RPI.Edit's MaterialUtils, as needed according to library dependencies.
This is in preparation for the material pipeline abstraction work, see https://github.com/o3de/sig-graphics-audio/blob/main/rfcs/MaterialPipelineAbstraction.md

Signed-off-by: santorac <[email protected]>

santorac 2 lat temu
rodzic
commit
0f68b0a922

+ 0 - 3
Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h

@@ -107,9 +107,6 @@ namespace AZ
                 bool elevateWarnings = true,
                 AZStd::unordered_set<AZStd::string>* sourceDependencies = nullptr) const;
 
-            //! Inspects the content of the MaterialPropertyValue to see if it is a string that appears to be an image file path.
-            static bool LooksLikeImageFileReference(const MaterialPropertyValue& value);
-
         private:
 
             void ApplyPropertiesToAssetCreator(

+ 2 - 5
Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialUtils.h

@@ -72,11 +72,8 @@ namespace AZ
                 const AZStd::string_view* acceptedFieldNames, uint32_t acceptedFieldNameCount,
                 const rapidjson::Value& object, JsonDeserializerContext& context, JsonSerializationResult::ResultCode& result);
 
-            //! Materials assets can either be finalized during asset-processing time or when materials are loaded at runtime.
-            //! Finalizing during asset processing reduces load times and obfuscates the material data.
-            //! Waiting to finalize at load time reduces dependencies on the material type data, resulting in fewer asset rebuilds and less time spent processing assets.
-            //! Removing the dependency on the material type data will require special handling of material type asset dependencies when loading and reloading materials.
-            bool BuildersShouldFinalizeMaterialAssets();
+            //! Inspects the content of the MaterialPropertyValue to see if it is a string that appears to be an image file path.
+            bool LooksLikeImageFileReference(const MaterialPropertyValue& value);
         }
     }
 }

+ 2 - 0
Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp

@@ -35,6 +35,7 @@
 #include <BuilderComponent.h>
 #include <Common/AnyAssetBuilder.h>
 #include <Material/MaterialBuilder.h>
+#include <Material/MaterialTypeBuilder.h>
 #include <ResourcePool/ResourcePoolBuilder.h>
 #include <Pass/PassBuilder.h>
 
@@ -78,6 +79,7 @@ namespace AZ
         {
             // Register asset workers
             m_assetWorkers.emplace_back(MakeAssetBuilder<MaterialBuilder>());
+            m_assetWorkers.emplace_back(MakeAssetBuilder<MaterialTypeBuilder>());
             m_assetWorkers.emplace_back(MakeAssetBuilder<ResourcePoolBuilder>());
             m_assetWorkers.emplace_back(MakeAssetBuilder<AnyAssetBuilder>());
             m_assetWorkers.emplace_back(MakeAssetBuilder<PassBuilder>());

+ 89 - 392
Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp

@@ -7,29 +7,12 @@
  */
 
 #include "MaterialBuilder.h"
-#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
-#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
+#include <Material/MaterialBuilderUtils.h>
+
 #include <Atom/RPI.Edit/Material/MaterialUtils.h>
-#include <Atom/RPI.Edit/Common/AssetUtils.h>
-#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
 #include <Atom/RPI.Edit/Common/JsonUtils.h>
 #include <AzCore/Serialization/Json/JsonUtils.h>
-
-#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
-#include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h>
-#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
-
-#include <AzFramework/IO/LocalFileIO.h>
-#include <AzFramework/StringFunc/StringFunc.h>
-#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
-#include <AzToolsFramework/Debug/TraceContext.h>
-
-#include <AssetBuilderSDK/AssetBuilderSDK.h>
 #include <AssetBuilderSDK/SerializationDependencies.h>
-
-#include <AzCore/IO/IOUtils.h>
-#include <AzCore/IO/Path/Path.h>
-#include <AzCore/Serialization/Json/JsonSerialization.h>
 #include <AzCore/Settings/SettingsRegistry.h>
 
 namespace AZ
@@ -41,22 +24,19 @@ namespace AZ
             [[maybe_unused]] static constexpr char const MaterialBuilderName[] = "MaterialBuilder";
         }
 
-        const char* MaterialBuilder::JobKey = "Atom Material Builder";
+        const char* MaterialBuilder::JobKey = "Material Builder";
 
         AZStd::string MaterialBuilder::GetBuilderSettingsFingerprint() const
         {
-            return
-                AZStd::string::format("[BuildersShouldFinalizeMaterialAssets=%d]", MaterialUtils::BuildersShouldFinalizeMaterialAssets()) +
-                AZStd::string::format("[ShouldOutputAllPropertiesMaterial=%d]", ShouldOutputAllPropertiesMaterial());
+            return AZStd::string::format("[BuildersShouldFinalizeMaterialAssets=%d]", MaterialBuilderUtils::BuildersShouldFinalizeMaterialAssets());
         }
 
         void MaterialBuilder::RegisterBuilder()
         {
             AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
             materialBuilderDescriptor.m_name = JobKey;
-            materialBuilderDescriptor.m_version = 133; // Preload material dependencies
+            materialBuilderDescriptor.m_version = 134; // Separate material type builder
             materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
-            materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
             materialBuilderDescriptor.m_busId = azrtti_typeid<MaterialBuilder>();
             materialBuilderDescriptor.m_createJobFunction = AZStd::bind(&MaterialBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
             materialBuilderDescriptor.m_processJobFunction = AZStd::bind(&MaterialBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
@@ -83,121 +63,6 @@ namespace AZ
             return warningsAsErrors;
         }
 
-        bool MaterialBuilder::ShouldOutputAllPropertiesMaterial() const
-        {
-            // Enable this setting to generate a default source material file containing an explicit list of all properties and their
-            // default values. This is primarily used by artists and developers scraping data from the materials and should only be enabled
-            // as needed by those users.
-            bool value = false;
-            if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
-            {
-                settingsRegistry->Get(value, "/O3DE/Atom/RPI/MaterialTypeBuilder/CreateAllPropertiesMaterial");
-            }
-            return value;
-        }
-
-        //! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path.
-        //! This can include both source dependencies and a single job dependency, but will include only source dependencies if the file is not found.
-        //! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back
-        //! to the AssetBuilderSDK::CreateJobsResponse.
-        //! @param currentFilePath The path of the .material or .materialtype file being processed
-        //! @param referencedParentPath The path to the referenced file as it appears in the current file
-        //! @param jobKey The job key for the job that is expected to process the referenced file
-        //! @param jobDependencies Dependencies may be added to this list
-        //! @param sourceDependencies Dependencies may be added to this list
-        //! @param forceOrderOnce If true, any job dependencies will use JobDependencyType::OrderOnce. Use this if the builder will only ever need to get the AssetId
-        //!                       of the referenced file but will not need to load the asset.
-        void AddPossibleDependencies(
-            const AZStd::string& currentFilePath,
-            const AZStd::string& referencedParentPath,
-            const char* jobKey,
-            AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
-            AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencies,
-            bool forceOrderOnce = false,
-            AZStd::optional<AZ::u32> productSubId = AZStd::nullopt)
-        {
-            bool dependencyFileFound = false;
-            
-            const bool currentFileIsMaterial = AzFramework::StringFunc::Path::IsExtension(currentFilePath.c_str(), MaterialSourceData::Extension);
-            const bool referencedFileIsMaterialType = AzFramework::StringFunc::Path::IsExtension(referencedParentPath.c_str(), MaterialTypeSourceData::Extension);
-            const bool shouldFinalizeMaterialAssets = MaterialUtils::BuildersShouldFinalizeMaterialAssets();
-
-            AZStd::vector<AZStd::string> possibleDependencies = RPI::AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath);
-            for (auto& file : possibleDependencies)
-            {
-                // The first path found is the highest priority, and will have a job dependency, as this is the one
-                // the builder will actually use
-                if (!dependencyFileFound)
-                {
-                    AZ::Data::AssetInfo sourceInfo;
-                    AZStd::string watchFolder;
-                    AzToolsFramework::AssetSystemRequestBus::BroadcastResult(dependencyFileFound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, file.c_str(), sourceInfo, watchFolder);
-
-                    if (dependencyFileFound)
-                    {
-                        AssetBuilderSDK::JobDependency jobDependency;
-                        jobDependency.m_jobKey = jobKey;
-                        jobDependency.m_sourceFile.m_sourceFileDependencyPath = file;
-                        jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
-                        
-                        if(productSubId)
-                        {
-                            jobDependency.m_productSubIds.push_back(productSubId.value());
-                        }
-                        
-                        // If we aren't finalizing material assets, then a normal job dependency isn't needed because the MaterialTypeAsset data won't be used.
-                        // However, we do still need at least an OrderOnce dependency to ensure the Asset Processor knows about the material type asset so the builder can get it's AssetId.
-                        // This can significantly reduce AP processing time when a material type or its shaders are edited.
-                        if (forceOrderOnce || (currentFileIsMaterial && referencedFileIsMaterialType && !shouldFinalizeMaterialAssets))
-                        {
-                            jobDependency.m_type = AssetBuilderSDK::JobDependencyType::OrderOnce;
-                        }
-
-                        jobDependencies.push_back(jobDependency);
-                    }
-                    else
-                    {
-                        // The file was not found so we can't add a job dependency. But we add a source dependency instead so if a file
-                        // shows up later at this location, it will wake up the builder to try again.
-
-                        AssetBuilderSDK::SourceFileDependency sourceDependency;
-                        sourceDependency.m_sourceFileDependencyPath = file;
-                        sourceDependencies.push_back(sourceDependency);
-                    }
-                }
-            }
-        }
-
-        void AddPossibleImageDependencies(
-            const AZStd::string& currentFilePath,
-            const AZStd::string& imageFilePath,
-            AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
-            AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencies)
-        {
-            if (imageFilePath.empty())
-            {
-                return;
-            }
-
-            AZStd::string ext;
-            AzFramework::StringFunc::Path::GetExtension(imageFilePath.c_str(), ext, false);
-            AZStd::to_upper(ext.begin(), ext.end());
-            AZStd::string jobKey = "Image Compile: " + ext;
-
-            if (ext.empty())
-            {
-                return;
-            }
-
-            bool forceOrderOnce = true;
-            AddPossibleDependencies(currentFilePath,
-                imageFilePath,
-                jobKey.c_str(),
-                jobDependencies,
-                sourceDependencies,
-                forceOrderOnce);
-        }
-
         void MaterialBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
         {
             if (m_isShuttingDown)
@@ -211,158 +76,82 @@ namespace AZ
             outputJobDescriptor.m_jobKey = JobKey;
             outputJobDescriptor.m_additionalFingerprintInfo = GetBuilderSettingsFingerprint();
 
-            // Load the file so we can detect and report dependencies.
-            // If the file is a .materialtype, report dependencies on the .shader files.
-            // If the file is a .material, report a dependency on the .materialtype and parent .material file
-            {
-                AZStd::string fullSourcePath;
-                AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullSourcePath, true);
-
-                auto loadOutcome = JsonSerializationUtils::ReadJsonFile(fullSourcePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
-                if (!loadOutcome.IsSuccess())
-                {
-                    AZ_Error(MaterialBuilderName, false, "%s", loadOutcome.GetError().c_str());
-                    return;
-                }
-
-                rapidjson::Document& document = loadOutcome.GetValue();
-
-                const bool isMaterialTypeFile = AzFramework::StringFunc::Path::IsExtension(request.m_sourceFile.c_str(), MaterialTypeSourceData::Extension);
-                if (isMaterialTypeFile)
-                {
-                    MaterialUtils::ImportedJsonFiles importedJsonFiles;
-                    auto materialTypeSourceData = MaterialUtils::LoadMaterialTypeSourceData(fullSourcePath, &document, &importedJsonFiles);
-
-                    if (!materialTypeSourceData.IsSuccess())
-                    {
-                        return;
-                    }
+            AZStd::string fullSourcePath;
+            AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullSourcePath, true);
 
-                    for (auto& importedJsonFile : importedJsonFiles)
-                    {
-                        AssetBuilderSDK::SourceFileDependency sourceDependency;
-                        sourceDependency.m_sourceFileDependencyPath = importedJsonFile;
-                        response.m_sourceFileDependencyList.push_back(sourceDependency);
-                    }
+            auto loadOutcome = JsonSerializationUtils::ReadJsonFile(fullSourcePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
+            if (!loadOutcome.IsSuccess())
+            {
+                AZ_Error(MaterialBuilderName, false, "%s", loadOutcome.GetError().c_str());
+                return;
+            }
 
-                    for (auto& shader : materialTypeSourceData.GetValue().m_shaderCollection)
-                    {
-                        AddPossibleDependencies(request.m_sourceFile,
-                            shader.m_shaderFilePath,
-                            "Shader Asset",
-                            outputJobDescriptor.m_jobDependencyList,
-                            response.m_sourceFileDependencyList,
-                            false,
-                            0);
-                    }
+            rapidjson::Document& document = loadOutcome.GetValue();
 
-                    auto addFunctorDependencies = [&outputJobDescriptor, &request, &response](const AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>>& functors)
-                    {
-                        for (auto& functor : functors)
-                        {
-                            const auto& dependencies = functor->GetActualSourceData()->GetAssetDependencies();
-
-                            for (const MaterialFunctorSourceData::AssetDependency& dependency : dependencies)
-                            {
-                                AddPossibleDependencies(request.m_sourceFile,
-                                    dependency.m_sourceFilePath,
-                                    dependency.m_jobKey.c_str(),
-                                    outputJobDescriptor.m_jobDependencyList,
-                                    response.m_sourceFileDependencyList);
-                            }
-                        }
-                    };
-
-                    addFunctorDependencies(materialTypeSourceData.GetValue().m_materialFunctorSourceData);
-
-                    materialTypeSourceData.GetValue().EnumeratePropertyGroups([addFunctorDependencies](const MaterialTypeSourceData::PropertyGroupStack& propertyGroupStack)
-                        {
-                            addFunctorDependencies(propertyGroupStack.back()->GetFunctors());
-                            return true;
-                        });
-
-                    materialTypeSourceData.GetValue().EnumerateProperties(
-                        [&request, &response, &outputJobDescriptor](const MaterialTypeSourceData::PropertyDefinition* property, const MaterialNameContext&)
-                        {
-                            if (property->m_dataType == MaterialPropertyDataType::Image && MaterialSourceData::LooksLikeImageFileReference(property->m_value))
-                            {
-                                AddPossibleImageDependencies(
-                                    request.m_sourceFile,
-                                    property->m_value.GetValue<AZStd::string>(),
-                                    outputJobDescriptor.m_jobDependencyList,
-                                    response.m_sourceFileDependencyList);
-                            }
-                            return true;
-                        });
-                }
-                else // it's a .material file
-                {
-                    // Note we don't use the LoadMaterial() utility function or JsonSerializer here because we don't care about fully
-                    // processing the material file at this point and reporting on the many things that could go wrong. We just want
-                    // to report the parent material and material type dependencies. So using rapidjson directly is actually simpler.
+            // Note we don't use the LoadMaterial() utility function or JsonSerializer here because we don't care about fully
+            // processing the material file at this point and reporting on the many things that could go wrong. We just want
+            // to report the parent material and material type dependencies. So using rapidjson directly is actually simpler.
 
-                    AZStd::string materialTypePath;
-                    AZStd::string parentMaterialPath;
+            AZStd::string materialTypePath;
+            AZStd::string parentMaterialPath;
 
-                    auto& materialJson = document;
+            auto& materialJson = document;
 
-                    const char* const materialTypeField = "materialType";
-                    const char* const parentMaterialField = "parentMaterial";
+            const char* const materialTypeField = "materialType";
+            const char* const parentMaterialField = "parentMaterial";
 
-                    if (materialJson.IsObject() && materialJson.HasMember(materialTypeField) && materialJson[materialTypeField].IsString())
-                    {
-                        materialTypePath = materialJson[materialTypeField].GetString();
-                    }
+            if (materialJson.IsObject() && materialJson.HasMember(materialTypeField) && materialJson[materialTypeField].IsString())
+            {
+                materialTypePath = materialJson[materialTypeField].GetString();
+            }
 
-                    if (materialJson.IsObject() && materialJson.HasMember(parentMaterialField) && materialJson[parentMaterialField].IsString())
-                    {
-                        parentMaterialPath = materialJson[parentMaterialField].GetString();
-                    }
+            if (materialJson.IsObject() && materialJson.HasMember(parentMaterialField) && materialJson[parentMaterialField].IsString())
+            {
+                parentMaterialPath = materialJson[parentMaterialField].GetString();
+            }
 
-                    if (parentMaterialPath.empty())
-                    {
-                        parentMaterialPath = materialTypePath;
-                    }
+            if (parentMaterialPath.empty())
+            {
+                parentMaterialPath = materialTypePath;
+            }
 
-                    // Register dependency on the parent material source file so we can load it and use it's data to build this variant material.
-                    // Note, we don't need a direct dependency on the material type because the parent material will depend on it.
-                    AddPossibleDependencies(request.m_sourceFile,
-                        parentMaterialPath,
-                        JobKey,
-                        outputJobDescriptor.m_jobDependencyList,
-                        response.m_sourceFileDependencyList,
-                        false,
-                        0);
-
-                    // Even though above we were able to get away without deserializing the material json, we do need to deserialize here in order
-                    // to easily read the property values. Note that with the latest .material file format, it actually wouldn't be too hard to
-                    // just read the raw json, it's just a map of property name to property value. But we also are maintaining backward compatible
-                    // support for an older file format that nests property values rather than using a flat list. By deserializing we leave it up
-                    // to the MaterialSourceData class to provide that backward compatibility (see MaterialSourceData::ConvertToNewDataFormat()).
+            // Register dependency on the parent material source file so we can load it and use it's data to build this variant material.
+            // Note, we don't need a direct dependency on the material type because the parent material will depend on it.
+            MaterialBuilderUtils::AddPossibleDependencies(request.m_sourceFile,
+                parentMaterialPath,
+                JobKey,
+                outputJobDescriptor.m_jobDependencyList,
+                response.m_sourceFileDependencyList,
+                false,
+                0);
+
+            // Even though above we were able to get away without deserializing the material json, we do need to deserialize here in order
+            // to easily read the property values. Note that with the latest .material file format, it actually wouldn't be too hard to
+            // just read the raw json, it's just a map of property name to property value. But we also are maintaining backward compatible
+            // support for an older file format that nests property values rather than using a flat list. By deserializing we leave it up
+            // to the MaterialSourceData class to provide that backward compatibility (see MaterialSourceData::ConvertToNewDataFormat()).
                     
-                    auto materialSourceData = MaterialUtils::LoadMaterialSourceData(fullSourcePath, &materialJson);
-                    if (materialSourceData.IsSuccess())
-                    {
-                        for (auto& [propertyId, propertyValue] : materialSourceData.GetValue().GetPropertyValues())
-                        {
-                            AZ_UNUSED(propertyId);
+            auto materialSourceData = MaterialUtils::LoadMaterialSourceData(fullSourcePath, &materialJson);
+            if (materialSourceData.IsSuccess())
+            {
+                for (auto& [propertyId, propertyValue] : materialSourceData.GetValue().GetPropertyValues())
+                {
+                    AZ_UNUSED(propertyId);
                             
-                            if (MaterialSourceData::LooksLikeImageFileReference(propertyValue))
-                            {
-                                AddPossibleImageDependencies(
-                                    request.m_sourceFile,
-                                    propertyValue.GetValue<AZStd::string>(),
-                                    outputJobDescriptor.m_jobDependencyList,
-                                    response.m_sourceFileDependencyList);
-                            }
-                        }
-                        
-                    }
-                    else
+                    if (MaterialUtils::LooksLikeImageFileReference(propertyValue))
                     {
-                        AZ_Warning(MaterialBuilderName, false, "Could not report dependencies for Image properties because the material json couldn't be loaded.");
+                        MaterialBuilderUtils::AddPossibleImageDependencies(
+                            request.m_sourceFile,
+                            propertyValue.GetValue<AZStd::string>(),
+                            outputJobDescriptor.m_jobDependencyList,
+                            response.m_sourceFileDependencyList);
                     }
                 }
+                        
+            }
+            else
+            {
+                AZ_Warning(MaterialBuilderName, false, "Could not report dependencies for Image properties because the material json couldn't be loaded.");
             }
             
             // Create the output jobs for each platform
@@ -381,24 +170,6 @@ namespace AZ
             response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
         }
 
-        AZ::Data::Asset<MaterialTypeAsset> CreateMaterialTypeAsset(AZStd::string_view materialTypeSourceFilePath, rapidjson::Document& json)
-        {
-            auto materialType = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath, &json);
-
-            if (!materialType.IsSuccess())
-            {
-                return  {};
-            }
-
-            auto materialTypeAssetOutcome = materialType.GetValue().CreateMaterialTypeAsset(Uuid::CreateRandom(), materialTypeSourceFilePath, true);
-            if (!materialTypeAssetOutcome.IsSuccess())
-            {
-                return  {};
-            }
-
-            return materialTypeAssetOutcome.GetValue();
-        }
-        
         AZ::Data::Asset<MaterialAsset> MaterialBuilder::CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const
         {
             auto material = MaterialUtils::LoadMaterialSourceData(materialSourceFilePath, &json, true);
@@ -408,7 +179,7 @@ namespace AZ
                 return {};
             }
 
-            MaterialAssetProcessingMode processingMode = MaterialUtils::BuildersShouldFinalizeMaterialAssets() ? MaterialAssetProcessingMode::PreBake : MaterialAssetProcessingMode::DeferredBake;
+            MaterialAssetProcessingMode processingMode = MaterialBuilderUtils::BuildersShouldFinalizeMaterialAssets() ? MaterialAssetProcessingMode::PreBake : MaterialAssetProcessingMode::DeferredBake;
 
             auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, processingMode, ShouldReportMaterialAssetWarningsAsErrors());
             if (!materialAssetOutcome.IsSuccess())
@@ -434,8 +205,6 @@ namespace AZ
                 return;
             }
 
-            const bool isMaterialTypeFile = AzFramework::StringFunc::Path::IsExtension(request.m_sourceFile.c_str(), MaterialTypeSourceData::Extension);
-
             AZStd::string fullSourcePath;
             AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullSourcePath, true);
 
@@ -451,109 +220,37 @@ namespace AZ
             AZStd::string materialProductPath;
             AZStd::string fileName;
             AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), fileName);
-            AzFramework::StringFunc::Path::ReplaceExtension(fileName, isMaterialTypeFile ? MaterialTypeAsset::Extension : MaterialAsset::Extension);
+            AzFramework::StringFunc::Path::ReplaceExtension(fileName, MaterialAsset::Extension);
 
             AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), fileName.c_str(), materialProductPath, true);
 
-            if (isMaterialTypeFile)
-            {
-                AZ::Data::Asset<MaterialTypeAsset> materialTypeAsset;
-
-                {
-                    AZ_TraceContext("Product", fileName);
-                    AZ_TracePrintf("MaterialBuilder", AZStd::string::format("Producing %s...", fileName.c_str()).c_str());
+            // Load the material file and create the MaterialAsset object
+            AZ::Data::Asset<MaterialAsset> materialAsset;
+            materialAsset = CreateMaterialAsset(request.m_sourceFile, document);
 
-                    // Load the material type file and create the MaterialTypeAsset object
-                    materialTypeAsset = CreateMaterialTypeAsset(fullSourcePath, document);
-
-                    if (!materialTypeAsset)
-                    {
-                        // Errors will have been reported above
-                        return;
-                    }
-
-                    // [ATOM-13190] Change this back to ST_BINARY. It's ST_XML temporarily for debugging.
-                    if (!AZ::Utils::SaveObjectToFile(materialProductPath, AZ::DataStream::ST_XML, materialTypeAsset.Get()))
-                    {
-                        AZ_Error(MaterialBuilderName, false, "Failed to save material type to file '%s'!", materialProductPath.c_str());
-                        return;
-                    }
-
-                    AssetBuilderSDK::JobProduct jobProduct;
-                    if (!AssetBuilderSDK::OutputObject(
-                        materialTypeAsset.Get(),
-                        materialProductPath,
-                        azrtti_typeid<RPI::MaterialTypeAsset>(),
-                        (u32)MaterialTypeProductSubId::MaterialTypeAsset,
-                        jobProduct))
-                    {
-                        AZ_Error(MaterialBuilderName, false, "Failed to output product dependencies.");
-                        return;
-                    }
-
-                    response.m_outputProducts.push_back(AZStd::move(jobProduct));
-                }
-
-                if(ShouldOutputAllPropertiesMaterial())
-                {
-                    AZStd::string defaultMaterialFileName;
-                    AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), defaultMaterialFileName);
-                    defaultMaterialFileName += "_AllProperties.material";
-
-                    AZStd::string defaultMaterialFilePath;
-                    AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), defaultMaterialFileName.c_str(), defaultMaterialFilePath, true);
-
-                    AZ_TraceContext("Product", defaultMaterialFileName);
-                    AZ_TracePrintf("MaterialBuilder", AZStd::string::format("Producing %s...", defaultMaterialFileName.c_str()).c_str());
-
-                    MaterialSourceData allPropertyDefaultsMaterial = MaterialSourceData::CreateAllPropertyDefaultsMaterial(materialTypeAsset, request.m_sourceFile);
-
-                    if (!JsonUtils::SaveObjectToFile(defaultMaterialFilePath, allPropertyDefaultsMaterial))
-                    {
-                        AZ_Warning(MaterialBuilderName, false, "Failed to save material reference file '%s'!", defaultMaterialFilePath.c_str());
-                    }
-                    else
-                    {
-                        AssetBuilderSDK::JobProduct defaultMaterialFileProduct;
-                        defaultMaterialFileProduct.m_dependenciesHandled = true; // This product is only for reference, not used at runtime
-                        defaultMaterialFileProduct.m_productFileName = defaultMaterialFilePath;
-                        defaultMaterialFileProduct.m_productSubID = (u32)MaterialTypeProductSubId::AllPropertiesMaterialSourceFile;
-                        response.m_outputProducts.push_back(AZStd::move(defaultMaterialFileProduct));
-                    }
-                }
-
-                response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
-            }
-            else
+            if (!materialAsset)
             {
-                // Load the material file and create the MaterialAsset object
-                AZ::Data::Asset<MaterialAsset> materialAsset;
-                materialAsset = CreateMaterialAsset(request.m_sourceFile, document);
-
-                if (!materialAsset)
-                {
-                    // Errors will have been reported above
-                    return;
-                }
+                // Errors will have been reported above
+                return;
+            }
 
-                // [ATOM-13190] Change this back to ST_BINARY. It's ST_XML temporarily for debugging.
-                if (!AZ::Utils::SaveObjectToFile(materialProductPath, AZ::DataStream::ST_XML, materialAsset.Get()))
-                {
-                    AZ_Error(MaterialBuilderName, false, "Failed to save material to file '%s'!", materialProductPath.c_str());
-                    return;
-                }
+            // [ATOM-13190] Change this back to ST_BINARY. It's ST_XML temporarily for debugging.
+            if (!AZ::Utils::SaveObjectToFile(materialProductPath, AZ::DataStream::ST_XML, materialAsset.Get()))
+            {
+                AZ_Error(MaterialBuilderName, false, "Failed to save material to file '%s'!", materialProductPath.c_str());
+                return;
+            }
 
-                AssetBuilderSDK::JobProduct jobProduct;
-                if (!AssetBuilderSDK::OutputObject(materialAsset.Get(), materialProductPath, azrtti_typeid<RPI::MaterialAsset>(), 0, jobProduct))
-                {
-                    AZ_Error(MaterialBuilderName, false, "Failed to output product dependencies.");
-                    return;
-                }
+            AssetBuilderSDK::JobProduct jobProduct;
+            if (!AssetBuilderSDK::OutputObject(materialAsset.Get(), materialProductPath, azrtti_typeid<RPI::MaterialAsset>(), 0, jobProduct))
+            {
+                AZ_Error(MaterialBuilderName, false, "Failed to output product dependencies.");
+                return;
+            }
 
-                response.m_outputProducts.push_back(AZStd::move(jobProduct));
+            response.m_outputProducts.push_back(AZStd::move(jobProduct));
 
-                response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
-            }
+            response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
         }
 
         void MaterialBuilder::ShutDown()

+ 0 - 7
Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.h

@@ -41,15 +41,8 @@ namespace AZ
 
         private:
 
-            enum class MaterialTypeProductSubId : u32
-            {
-                MaterialTypeAsset = 0,
-                AllPropertiesMaterialSourceFile = 1
-            };
-
             AZ::Data::Asset<MaterialAsset> CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const;
             bool ShouldReportMaterialAssetWarningsAsErrors() const;
-            bool ShouldOutputAllPropertiesMaterial() const;
             AZStd::string GetBuilderSettingsFingerprint() const;
             
             bool m_isShuttingDown = false;

+ 127 - 0
Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilderUtils.cpp

@@ -0,0 +1,127 @@
+/*
+ * 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 "MaterialBuilderUtils.h"
+#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
+#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
+#include <Atom/RPI.Edit/Common/AssetUtils.h>
+#include <AzCore/Settings/SettingsRegistry.h>
+
+namespace AZ::RPI::MaterialBuilderUtils
+{
+    void AddPossibleDependencies(
+        const AZStd::string& currentFilePath,
+        const AZStd::string& referencedParentPath,
+        const char* jobKey,
+        AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
+        AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencies,
+        bool forceOrderOnce,
+        AZStd::optional<AZ::u32> productSubId)
+    {
+        bool dependencyFileFound = false;
+            
+        const bool currentFileIsMaterial = AzFramework::StringFunc::Path::IsExtension(currentFilePath.c_str(), MaterialSourceData::Extension);
+        const bool referencedFileIsMaterialType = AzFramework::StringFunc::Path::IsExtension(referencedParentPath.c_str(), MaterialTypeSourceData::Extension);
+        const bool shouldFinalizeMaterialAssets = MaterialBuilderUtils::BuildersShouldFinalizeMaterialAssets();
+
+        AZStd::vector<AZStd::string> possibleDependencies = RPI::AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath);
+        for (auto& file : possibleDependencies)
+        {
+            // The first path found is the highest priority, and will have a job dependency, as this is the one
+            // the builder will actually use
+            if (!dependencyFileFound)
+            {
+                AZ::Data::AssetInfo sourceInfo;
+                AZStd::string watchFolder;
+                AzToolsFramework::AssetSystemRequestBus::BroadcastResult(dependencyFileFound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, file.c_str(), sourceInfo, watchFolder);
+
+                if (dependencyFileFound)
+                {
+                    AssetBuilderSDK::JobDependency jobDependency;
+                    jobDependency.m_jobKey = jobKey;
+                    jobDependency.m_sourceFile.m_sourceFileDependencyPath = file;
+                    jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
+                        
+                    if(productSubId)
+                    {
+                        jobDependency.m_productSubIds.push_back(productSubId.value());
+                    }
+                        
+                    // If we aren't finalizing material assets, then a normal job dependency isn't needed because the MaterialTypeAsset data won't be used.
+                    // However, we do still need at least an OrderOnce dependency to ensure the Asset Processor knows about the material type asset so the builder can get it's AssetId.
+                    // This can significantly reduce AP processing time when a material type or its shaders are edited.
+                    if (forceOrderOnce || (currentFileIsMaterial && referencedFileIsMaterialType && !shouldFinalizeMaterialAssets))
+                    {
+                        jobDependency.m_type = AssetBuilderSDK::JobDependencyType::OrderOnce;
+                    }
+
+                    jobDependencies.push_back(jobDependency);
+                }
+                else
+                {
+                    // The file was not found so we can't add a job dependency. But we add a source dependency instead so if a file
+                    // shows up later at this location, it will wake up the builder to try again.
+
+                    AssetBuilderSDK::SourceFileDependency sourceDependency;
+                    sourceDependency.m_sourceFileDependencyPath = file;
+                    sourceDependencies.push_back(sourceDependency);
+                }
+            }
+        }
+    }
+
+    void AddPossibleImageDependencies(
+        const AZStd::string& currentFilePath,
+        const AZStd::string& imageFilePath,
+        AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
+        AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencies)
+    {
+        if (imageFilePath.empty())
+        {
+            return;
+        }
+
+        AZStd::string ext;
+        AzFramework::StringFunc::Path::GetExtension(imageFilePath.c_str(), ext, false);
+        AZStd::to_upper(ext.begin(), ext.end());
+        AZStd::string jobKey = "Image Compile: " + ext;
+
+        if (ext.empty())
+        {
+            return;
+        }
+
+        bool forceOrderOnce = true;
+        AddPossibleDependencies(currentFilePath,
+            imageFilePath,
+            jobKey.c_str(),
+            jobDependencies,
+            sourceDependencies,
+            forceOrderOnce);
+    }
+
+    bool BuildersShouldFinalizeMaterialAssets()
+    {
+        // Disable this registry setting to improve iteration times when making changes to widely used shaders and material types,
+        // like standard PBR, that require a large number of model assets to be reprocessed by the AP. Disabling finalization will
+        // also disable build dependencies between materials and material types. Without those dependencies in place, loading and
+        // reloading material assets will require special handling because typical asset notifications will not be sent when
+        // dependencies are changed. Before, this option was disabled by default because of the long iteration times, but caused hot
+        // reload problems so we enabled it again. We should explore options for handling dependencies on standard PBR differently
+        // at the model builder level, and hopefully improve the iteration times that way. In that case, we should come back and
+        // remove the deferred-finalize option entirely.
+        bool shouldFinalize = true;
+
+        if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+        {
+            settingsRegistry->Get(shouldFinalize, "/O3DE/Atom/RPI/MaterialBuilder/FinalizeMaterialAssets");
+        }
+
+        return shouldFinalize;
+    }
+}

+ 55 - 0
Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilderUtils.h

@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <AssetBuilderSDK/AssetBuilderSDK.h>
+
+namespace AZ
+{
+    namespace RPI
+    {
+        namespace MaterialBuilderUtils
+        {
+
+            //! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path.
+            //! This can include both source dependencies and a single job dependency, but will include only source dependencies if the file is not found.
+            //! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back
+            //! to the AssetBuilderSDK::CreateJobsResponse.
+            //! @param currentFilePath The path of the .material or .materialtype file being processed
+            //! @param referencedParentPath The path to the referenced file as it appears in the current file
+            //! @param jobKey The job key for the job that is expected to process the referenced file
+            //! @param jobDependencies Dependencies may be added to this list
+            //! @param sourceDependencies Dependencies may be added to this list
+            //! @param forceOrderOnce If true, any job dependencies will use JobDependencyType::OrderOnce. Use this if the builder will only ever need to get the AssetId
+            //!                       of the referenced file but will not need to load the asset.
+            void AddPossibleDependencies(
+                const AZStd::string& currentFilePath,
+                const AZStd::string& referencedParentPath,
+                const char* jobKey,
+                AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
+                AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencies,
+                bool forceOrderOnce = false,
+                AZStd::optional<AZ::u32> productSubId = AZStd::nullopt);
+
+
+            void AddPossibleImageDependencies(
+                const AZStd::string& currentFilePath,
+                const AZStd::string& imageFilePath,
+                AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
+                AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencies);
+
+            //! Materials assets can either be finalized during asset-processing time or when materials are loaded at runtime.
+            //! Finalizing during asset processing reduces load times and obfuscates the material data.
+            //! Waiting to finalize at load time reduces dependencies on the material type data, resulting in fewer asset rebuilds and less time spent processing assets.
+            //! Removing the dependency on the material type data will require special handling of material type asset dependencies when loading and reloading materials.
+            bool BuildersShouldFinalizeMaterialAssets();
+        }
+
+    } // namespace RPI
+} // namespace AZ

+ 294 - 0
Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialTypeBuilder.cpp

@@ -0,0 +1,294 @@
+/*
+ * 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 "MaterialTypeBuilder.h"
+#include <Material/MaterialBuilderUtils.h>
+
+#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
+#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
+#include <Atom/RPI.Edit/Material/MaterialUtils.h>
+#include <Atom/RPI.Edit/Common/JsonUtils.h>
+#include <AzCore/Serialization/Json/JsonUtils.h>
+#include <AzCore/Settings/SettingsRegistry.h>
+#include <AzToolsFramework/Debug/TraceContext.h>
+#include <AssetBuilderSDK/SerializationDependencies.h>
+
+namespace AZ
+{
+    namespace RPI
+    {
+        namespace
+        {
+            [[maybe_unused]] static constexpr char const MaterialTypeBuilderName[] = "MaterialTypeBuilder";
+        }
+
+        const char* MaterialTypeBuilder::JobKey = "Material Type Builder";
+
+        AZStd::string MaterialTypeBuilder::GetBuilderSettingsFingerprint() const
+        {
+            return AZStd::string::format("[ShouldOutputAllPropertiesMaterial=%d]", ShouldOutputAllPropertiesMaterial());
+        }
+
+        void MaterialTypeBuilder::RegisterBuilder()
+        {
+            AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
+            materialBuilderDescriptor.m_name = JobKey;
+            materialBuilderDescriptor.m_version = 1; // Split material type builder
+            materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
+            materialBuilderDescriptor.m_busId = azrtti_typeid<MaterialTypeBuilder>();
+            materialBuilderDescriptor.m_createJobFunction = AZStd::bind(&MaterialTypeBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
+            materialBuilderDescriptor.m_processJobFunction = AZStd::bind(&MaterialTypeBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
+
+            materialBuilderDescriptor.m_analysisFingerprint = GetBuilderSettingsFingerprint();
+
+            BusConnect(materialBuilderDescriptor.m_busId);
+
+            AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, materialBuilderDescriptor);
+        }
+
+        MaterialTypeBuilder::~MaterialTypeBuilder()
+        {
+            BusDisconnect();
+        }
+
+        bool MaterialTypeBuilder::ShouldOutputAllPropertiesMaterial() const
+        {
+            // Enable this setting to generate a default source material file containing an explicit list of all properties and their
+            // default values. This is primarily used by artists and developers scraping data from the materials and should only be enabled
+            // as needed by those users.
+            bool value = false;
+            if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+            {
+                settingsRegistry->Get(value, "/O3DE/Atom/RPI/MaterialTypeBuilder/CreateAllPropertiesMaterial");
+            }
+            return value;
+        }
+
+        void MaterialTypeBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
+        {
+            if (m_isShuttingDown)
+            {
+                response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
+                return;
+            }
+
+            // We'll build up this one JobDescriptor and reuse it to register each of the platforms
+            AssetBuilderSDK::JobDescriptor outputJobDescriptor;
+            outputJobDescriptor.m_jobKey = JobKey;
+            outputJobDescriptor.m_additionalFingerprintInfo = GetBuilderSettingsFingerprint();
+
+            AZStd::string fullSourcePath;
+            AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullSourcePath, true);
+
+            MaterialUtils::ImportedJsonFiles importedJsonFiles;
+            auto materialTypeSourceData = MaterialUtils::LoadMaterialTypeSourceData(fullSourcePath, nullptr, &importedJsonFiles);
+
+            if (!materialTypeSourceData.IsSuccess())
+            {
+                return;
+            }
+
+            for (auto& importedJsonFile : importedJsonFiles)
+            {
+                AssetBuilderSDK::SourceFileDependency sourceDependency;
+                sourceDependency.m_sourceFileDependencyPath = importedJsonFile;
+                response.m_sourceFileDependencyList.push_back(sourceDependency);
+            }
+
+            for (auto& shader : materialTypeSourceData.GetValue().m_shaderCollection)
+            {
+                MaterialBuilderUtils::AddPossibleDependencies(request.m_sourceFile,
+                    shader.m_shaderFilePath,
+                    "Shader Asset",
+                    outputJobDescriptor.m_jobDependencyList,
+                    response.m_sourceFileDependencyList,
+                    false,
+                    0);
+            }
+
+            auto addFunctorDependencies = [&outputJobDescriptor, &request, &response](const AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>>& functors)
+            {
+                for (auto& functor : functors)
+                {
+                    const auto& dependencies = functor->GetActualSourceData()->GetAssetDependencies();
+
+                    for (const MaterialFunctorSourceData::AssetDependency& dependency : dependencies)
+                    {
+                        MaterialBuilderUtils::AddPossibleDependencies(request.m_sourceFile,
+                            dependency.m_sourceFilePath,
+                            dependency.m_jobKey.c_str(),
+                            outputJobDescriptor.m_jobDependencyList,
+                            response.m_sourceFileDependencyList);
+                    }
+                }
+            };
+
+            addFunctorDependencies(materialTypeSourceData.GetValue().m_materialFunctorSourceData);
+
+            materialTypeSourceData.GetValue().EnumeratePropertyGroups([addFunctorDependencies](const MaterialTypeSourceData::PropertyGroupStack& propertyGroupStack)
+                {
+                    addFunctorDependencies(propertyGroupStack.back()->GetFunctors());
+                    return true;
+                });
+
+            materialTypeSourceData.GetValue().EnumerateProperties(
+                [&request, &response, &outputJobDescriptor](const MaterialTypeSourceData::PropertyDefinition* property, const MaterialNameContext&)
+                {
+                    if (property->m_dataType == MaterialPropertyDataType::Image && MaterialUtils::LooksLikeImageFileReference(property->m_value))
+                    {
+                        MaterialBuilderUtils::AddPossibleImageDependencies(
+                            request.m_sourceFile,
+                            property->m_value.GetValue<AZStd::string>(),
+                            outputJobDescriptor.m_jobDependencyList,
+                            response.m_sourceFileDependencyList);
+                    }
+                    return true;
+                });
+            
+            // Create the output jobs for each platform
+            for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
+            {
+                outputJobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
+
+                for (auto& jobDependency : outputJobDescriptor.m_jobDependencyList)
+                {
+                    jobDependency.m_platformIdentifier = platformInfo.m_identifier;
+                }
+
+                response.m_createJobOutputs.push_back(outputJobDescriptor);
+            }
+
+            response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
+        }
+
+        AZ::Data::Asset<MaterialTypeAsset> CreateMaterialTypeAsset(AZStd::string_view materialTypeSourceFilePath, rapidjson::Document& json)
+        {
+            auto materialType = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath, &json);
+
+            if (!materialType.IsSuccess())
+            {
+                return  {};
+            }
+
+            auto materialTypeAssetOutcome = materialType.GetValue().CreateMaterialTypeAsset(Uuid::CreateRandom(), materialTypeSourceFilePath, true);
+            if (!materialTypeAssetOutcome.IsSuccess())
+            {
+                return  {};
+            }
+
+            return materialTypeAssetOutcome.GetValue();
+        }
+        
+        void MaterialTypeBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
+        {
+            AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
+
+            if (jobCancelListener.IsCancelled())
+            {
+                response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
+                return;
+            }
+            if (m_isShuttingDown)
+            {
+                response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
+                return;
+            }
+
+            AZStd::string fullSourcePath;
+            AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullSourcePath, true);
+
+            auto loadOutcome = JsonSerializationUtils::ReadJsonFile(fullSourcePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
+            if (!loadOutcome.IsSuccess())
+            {
+                AZ_Error(MaterialTypeBuilderName, false, "Failed to load material file: %s", loadOutcome.GetError().c_str());
+                return;
+            }
+
+            rapidjson::Document& document = loadOutcome.GetValue();
+
+            AZStd::string materialProductPath;
+            AZStd::string fileName;
+            AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), fileName);
+            AzFramework::StringFunc::Path::ReplaceExtension(fileName, MaterialTypeAsset::Extension);
+
+            AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), fileName.c_str(), materialProductPath, true);
+
+            AZ::Data::Asset<MaterialTypeAsset> materialTypeAsset;
+
+            {
+                AZ_TraceContext("Product", fileName);
+                AZ_TracePrintf("MaterialTypeBuilder", AZStd::string::format("Producing %s...", fileName.c_str()).c_str());
+
+                // Load the material type file and create the MaterialTypeAsset object
+                materialTypeAsset = CreateMaterialTypeAsset(fullSourcePath, document);
+
+                if (!materialTypeAsset)
+                {
+                    // Errors will have been reported above
+                    return;
+                }
+
+                // [ATOM-13190] Change this back to ST_BINARY. It's ST_XML temporarily for debugging.
+                if (!AZ::Utils::SaveObjectToFile(materialProductPath, AZ::DataStream::ST_XML, materialTypeAsset.Get()))
+                {
+                    AZ_Error(MaterialTypeBuilderName, false, "Failed to save material type to file '%s'!", materialProductPath.c_str());
+                    return;
+                }
+
+                AssetBuilderSDK::JobProduct jobProduct;
+                if (!AssetBuilderSDK::OutputObject(
+                    materialTypeAsset.Get(),
+                    materialProductPath,
+                    azrtti_typeid<RPI::MaterialTypeAsset>(),
+                    (u32)MaterialTypeProductSubId::MaterialTypeAsset,
+                    jobProduct))
+                {
+                    AZ_Error(MaterialTypeBuilderName, false, "Failed to output product dependencies.");
+                    return;
+                }
+
+                response.m_outputProducts.push_back(AZStd::move(jobProduct));
+            }
+
+            if(ShouldOutputAllPropertiesMaterial())
+            {
+                AZStd::string defaultMaterialFileName;
+                AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), defaultMaterialFileName);
+                defaultMaterialFileName += "_AllProperties.material";
+
+                AZStd::string defaultMaterialFilePath;
+                AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), defaultMaterialFileName.c_str(), defaultMaterialFilePath, true);
+
+                AZ_TraceContext("Product", defaultMaterialFileName);
+                AZ_TracePrintf("MaterialTypeBuilder", AZStd::string::format("Producing %s...", defaultMaterialFileName.c_str()).c_str());
+
+                MaterialSourceData allPropertyDefaultsMaterial = MaterialSourceData::CreateAllPropertyDefaultsMaterial(materialTypeAsset, request.m_sourceFile);
+
+                if (!JsonUtils::SaveObjectToFile(defaultMaterialFilePath, allPropertyDefaultsMaterial))
+                {
+                    AZ_Warning(MaterialTypeBuilderName, false, "Failed to save material reference file '%s'!", defaultMaterialFilePath.c_str());
+                }
+                else
+                {
+                    AssetBuilderSDK::JobProduct defaultMaterialFileProduct;
+                    defaultMaterialFileProduct.m_dependenciesHandled = true; // This product is only for reference, not used at runtime
+                    defaultMaterialFileProduct.m_productFileName = defaultMaterialFilePath;
+                    defaultMaterialFileProduct.m_productSubID = (u32)MaterialTypeProductSubId::AllPropertiesMaterialSourceFile;
+                    response.m_outputProducts.push_back(AZStd::move(defaultMaterialFileProduct));
+                }
+            }
+
+            response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
+        }
+
+        void MaterialTypeBuilder::ShutDown()
+        {
+            m_isShuttingDown = true;
+        }
+    } // namespace RPI
+} // namespace AZ

+ 55 - 0
Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialTypeBuilder.h

@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <AssetBuilderSDK/AssetBuilderBusses.h>
+#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
+#include <AzCore/JSON/document.h>
+
+namespace AZ
+{
+    namespace RPI
+    {
+        class MaterialTypeBuilder final
+            : public AssetBuilderSDK::AssetBuilderCommandBus::Handler
+        {
+        public:
+            AZ_TYPE_INFO(MaterialTypeBuilder, "{0D2D104F-9CC6-456E-88D9-24BCDA6C0465}");
+
+            static const char* JobKey;
+
+            MaterialTypeBuilder() = default;
+            ~MaterialTypeBuilder();
+
+            // Asset Builder Callback Functions
+            void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
+            void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
+
+            // AssetBuilderSDK::AssetBuilderCommandBus interface
+            void ShutDown() override;
+
+            /// Register to builder and listen to builder command
+            void RegisterBuilder();
+
+        private:
+
+            enum class MaterialTypeProductSubId : u32
+            {
+                MaterialTypeAsset = 0,
+                AllPropertiesMaterialSourceFile = 1
+            };
+
+            bool ShouldOutputAllPropertiesMaterial() const;
+            AZStd::string GetBuilderSettingsFingerprint() const;
+            
+            bool m_isShuttingDown = false;
+        };
+
+    } // namespace RPI
+} // namespace AZ

+ 6 - 4
Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp

@@ -7,6 +7,7 @@
  */
 
 #include <Model/MaterialAssetBuilderComponent.h>
+#include <Material/MaterialBuilderUtils.h>
 
 #include <AzCore/Asset/AssetCommon.h>
 #include <AzCore/Serialization/EditContext.h>
@@ -33,6 +34,7 @@
 #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
 #include <AzCore/Settings/SettingsRegistry.h>
 
+
 namespace AZ
 {
     namespace RPI
@@ -85,7 +87,7 @@ namespace AZ
                 materialTypeSource.m_sourceFileDependencyPath = materialTypePath;
 
                 AssetBuilderSDK::JobDependency jobDependency;
-                jobDependency.m_jobKey = "Atom Material Builder";
+                jobDependency.m_jobKey = "Material Type Builder";
                 jobDependency.m_sourceFile = materialTypeSource;
                 jobDependency.m_platformIdentifier = platformIdentifier;
                 jobDependency.m_productSubIds.push_back(0);
@@ -103,7 +105,7 @@ namespace AZ
 
                 // Note that without the normal job dependencies, materials may not load or reload consistently or in sync with shader and
                 // material type changes without manually monitoring and handling dependency changes.
-                jobDependency.m_type = MaterialUtils::BuildersShouldFinalizeMaterialAssets()
+                jobDependency.m_type = MaterialBuilderUtils::BuildersShouldFinalizeMaterialAssets()
                     ? AssetBuilderSDK::JobDependencyType::Order
                     : AssetBuilderSDK::JobDependencyType::OrderOnce;
 
@@ -119,7 +121,7 @@ namespace AZ
             RPI::MaterialConverterBus::BroadcastResult(conversionInfo, &RPI::MaterialConverterBus::Events::GetFingerprintInfo);
             fingerprintInfo.insert(conversionInfo);
              
-            fingerprintInfo.insert(AZStd::string::format("[BuildersShouldFinalizeMaterialAssets=%d]", MaterialUtils::BuildersShouldFinalizeMaterialAssets()));
+            fingerprintInfo.insert(AZStd::string::format("[BuildersShouldFinalizeMaterialAssets=%d]", MaterialBuilderUtils::BuildersShouldFinalizeMaterialAssets()));
         }
 
         void MaterialAssetBuilderComponent::Reflect(ReflectContext* context)
@@ -232,7 +234,7 @@ namespace AZ
                 }
             }
             
-            MaterialAssetProcessingMode processingMode = MaterialUtils::BuildersShouldFinalizeMaterialAssets() ? MaterialAssetProcessingMode::PreBake : MaterialAssetProcessingMode::DeferredBake;
+            MaterialAssetProcessingMode processingMode = MaterialBuilderUtils::BuildersShouldFinalizeMaterialAssets() ? MaterialAssetProcessingMode::PreBake : MaterialAssetProcessingMode::DeferredBake;
 
             // Build material assets. 
             for (auto& itr : materialSourceDataByUid)

+ 1 - 8
Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp

@@ -390,13 +390,6 @@ namespace AZ
             return Failure();
         }
 
-        /*static*/ bool MaterialSourceData::LooksLikeImageFileReference(const MaterialPropertyValue& value)
-        {
-            // If the source value type is a string, there are two possible property types: Image and Enum. If there is a "." in
-            // the string (for the extension) we can assume it's an Image file path.
-            return value.Is<AZStd::string>() && AzFramework::StringFunc::Contains(value.GetValue<AZStd::string>(), ".");
-        }
-
         void MaterialSourceData::ApplyPropertiesToAssetCreator(
             AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const
         {
@@ -411,7 +404,7 @@ namespace AZ
                     // If the source value type is a string, there are two possible property types: Image and Enum. If there is a "." in
                     // the string (for the extension) we assume it's an Image and look up the referenced Asset. Otherwise, we can assume
                     // it's an Enum value and just preserve the original string.
-                    if (LooksLikeImageFileReference(propertyValue))
+                    if (MaterialUtils::LooksLikeImageFileReference(propertyValue))
                     {
                         Data::Asset<ImageAsset> imageAsset;
 

+ 4 - 17
Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp

@@ -204,24 +204,11 @@ namespace AZ
                 }
             }
 
-            bool BuildersShouldFinalizeMaterialAssets()
+            bool LooksLikeImageFileReference(const MaterialPropertyValue& value)
             {
-                // Disable this registry setting to improve iteration times when making changes to widely used shaders and material types,
-                // like standard PBR, that require a large number of model assets to be reprocessed by the AP. Disabling finalization will
-                // also disable build dependencies between materials and material types. Without those dependencies in place, loading and
-                // reloading material assets will require special handling because typical asset notifications will not be sent when
-                // dependencies are changed. Before, this option was disabled by default because of the long iteration times, but caused hot
-                // reload problems so we enabled it again. We should explore options for handling dependencies on standard PBR differently
-                // at the model builder level, and hopefully improve the iteration times that way. In that case, we should come back and
-                // remove the deferred-finalize option entirely.
-                bool shouldFinalize = true;
-
-                if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
-                {
-                    settingsRegistry->Get(shouldFinalize, "/O3DE/Atom/RPI/MaterialBuilder/FinalizeMaterialAssets");
-                }
-
-                return shouldFinalize;
+                // If the source value type is a string, there are two possible property types: Image and Enum. If there is a "." in
+                // the string (for the extension) we can assume it's an Image file path.
+                return value.Is<AZStd::string>() && AzFramework::StringFunc::Contains(value.GetValue<AZStd::string>(), ".");
             }
         }
     }

+ 4 - 0
Gems/Atom/RPI/Code/atom_rpi_builders_files.cmake

@@ -13,6 +13,10 @@ set(FILES
     Source/RPI.Builders/Common/AnyAssetBuilder.h
     Source/RPI.Builders/Material/MaterialBuilder.cpp
     Source/RPI.Builders/Material/MaterialBuilder.h
+    Source/RPI.Builders/Material/MaterialBuilderUtils.cpp
+    Source/RPI.Builders/Material/MaterialBuilderUtils.h
+    Source/RPI.Builders/Material/MaterialTypeBuilder.cpp
+    Source/RPI.Builders/Material/MaterialTypeBuilder.h
     Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp
     Source/RPI.Builders/Model/MaterialAssetBuilderComponent.h
     Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp