text: /*
   * 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 <AzCore/Asset/AssetCommon.h>
  #include <AzCore/std/containers/vector.h>
  #include <AtomCore/std/containers/array_view.h>
  
  #include <Atom/RPI.Public/AssetInitBus.h>
  #include <Atom/RPI.Reflect/Base.h>
  #include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
  #include <Atom/RPI.Reflect/Material/MaterialPropertyValue.h>
  #include <Atom/RPI.Public/Material/MaterialReloadNotificationBus.h>
  
  #include <AzCore/EBus/Event.h>
  
  namespace UnitTest
  {
      class MaterialTests;
      class MaterialAssetTests;
  }
  
  namespace AZ
  {
      class ReflectContext;
  
      namespace RPI
      {
          class MaterialAssetHandler;
  
          //! MaterialAsset defines a single material, which can be used to create a Material instance for rendering at runtime.
          //! Use a MaterialAssetCreator to create a MaterialAsset.
          class MaterialAsset
              : public AZ::Data::AssetData
              , public Data::AssetBus::Handler
              , public MaterialReloadNotificationBus::Handler
              , public AssetInitBus::Handler
          {
              friend class MaterialVersionUpdate;
              friend class MaterialAssetCreator;
              friend class MaterialAssetHandler;
              friend class UnitTest::MaterialTests;
              friend class UnitTest::MaterialAssetTests;
  
          public:
              AZ_RTTI(MaterialAsset, "{522C7BE0-501D-463E-92C6-15184A2B7AD8}", AZ::Data::AssetData);
              AZ_CLASS_ALLOCATOR(MaterialAsset, SystemAllocator, 0);
  
              static const char* DisplayName;
              static const char* Group;
              static const char* Extension;
  
              static void Reflect(ReflectContext* context);
  
              MaterialAsset();
              virtual ~MaterialAsset();
  
              //! Returns the MaterialTypeAsset.
              const Data::Asset<MaterialTypeAsset>& GetMaterialTypeAsset() const;
  
              //! Returns the collection of all shaders that this material could run.
              const ShaderCollection& GetShaderCollection() const;
  
              //! The material may contain any number of MaterialFunctors.
              //! Material functors provide custom logic and calculations to configure shaders, render states, and more.
              //! See MaterialFunctor.h for details.
              const MaterialFunctorList& GetMaterialFunctors() const;
  
              //! Returns the shader resource group layout that has per-material frequency, which indicates most of the topology
              //! for a material's shaders.
              //! All shaders in a material will have the same per-material SRG layout.
              //! @param supervariantIndex: supervariant index to get the layout from.
              const RHI::Ptr<RHI::ShaderResourceGroupLayout>& GetMaterialSrgLayout(const SupervariantIndex& supervariantIndex) const;
  
              //! Same as above but accepts the supervariant name. There's a minor penalty when using this function
              //! because it will discover the index from the name.
              const RHI::Ptr<RHI::ShaderResourceGroupLayout>& GetMaterialSrgLayout(const AZ::Name& supervariantName) const;
  
              //! Just like the original GetMaterialSrgLayout() where it uses the index of the default supervariant.
              //! See the definition of DefaultSupervariantIndex.
              const RHI::Ptr<RHI::ShaderResourceGroupLayout>& GetMaterialSrgLayout() const;
  
              //! Returns the shader resource group layout that has per-object frequency. What constitutes an "object" is an
              //! agreement between the FeatureProcessor and the shaders, but an example might be world-transform for a model.
              //! All shaders in a material will have the same per-object SRG layout.
              //! @param supervariantIndex: supervariant index to get the layout from.
              const RHI::Ptr<RHI::ShaderResourceGroupLayout>& GetObjectSrgLayout(const SupervariantIndex& supervariantIndex) const;
  
              //! Same as above but accepts the supervariant name. There's a minor penalty when using this function
              //! because it will discover the index from the name.
              const RHI::Ptr<RHI::ShaderResourceGroupLayout>& GetObjectSrgLayout(const AZ::Name& supervariantName) const;
  
              //! Just like the original GetObjectSrgLayout() where it uses the index of the default supervariant.
              //! See the definition of DefaultSupervariantIndex.
              const RHI::Ptr<RHI::ShaderResourceGroupLayout>& GetObjectSrgLayout() const;
  
              //! Returns a layout that includes a list of MaterialPropertyDescriptors for each material property.
              const MaterialPropertiesLayout* GetMaterialPropertiesLayout() const;
  
              //! Returns the list of values for all properties in this material.
              //! The entries in this list align with the entries in the MaterialPropertiesLayout. Each AZStd::any is guaranteed 
              //! to have a value of type that matches the corresponding MaterialPropertyDescriptor.
              //! For images, the value will be of type ImageBinding.
              //!
              //! Note that even though material source data files contain only override values and inherit the rest from
              //! their parent material, they all get flattened at build time so every MaterialAsset has the full set of values.
              //!
              //! Calling GetPropertyValues() will automatically finalize the material asset if it isn't finalized already. The
              //! MaterialTypeAsset must be loaded and ready.
              const AZStd::vector<MaterialPropertyValue>& GetPropertyValues();
              
              //! Returns true if material was created in a finalize state, as opposed to being finalized after loading from disk.
              bool WasPreFinalized() const;
              
              //! Returns the list of raw values for all properties in this material, as listed in the source .material file(s), before the material asset was Finalized.
              //! 
              //! The MaterialAsset can be created in a "half-baked" state (see MaterialUtils::BuildersShouldFinalizeMaterialAssets) where
              //! minimal processing has been done because it did not yet have access to the MaterialTypeAsset. In that case, the list will
              //! be populated with values copied from the source .material file with little or no validation or other processing. It includes
              //! all parent .material files, with properties listed in low-to-high priority order. 
              //! This list will be empty however if the asset was finalized at build-time (i.e. WasPreFinalized() returns true).
              const AZStd::vector<AZStd::pair<Name, MaterialPropertyValue>>& GetRawPropertyValues() const;
  
          private:
              bool PostLoadInit() override;
              
              //! If the material asset is not finalized yet, this does the final processing of the raw property values to get the material asset ready to be used.
              //! MaterialTypeAsset must be valid before this is called.
              void Finalize(AZStd::function<void(const char*)> reportWarning = nullptr, AZStd::function<void(const char*)> reportError = nullptr);
  
              //! Checks the material type version and potentially applies a series of property changes (most common are simple property renames)
              //! based on the MaterialTypeAsset's version update procedure.
              void ApplyVersionUpdates();
  
              //! Called by asset creators to assign the asset to a ready state.
              void SetReady();
  
              // AssetBus overrides...
              void OnAssetReloaded(Data::Asset<Data::AssetData> asset) override;
              void OnAssetReady(Data::Asset<Data::AssetData> asset) override;
  
              //! Replaces the MaterialTypeAsset when a reload occurs
              void ReinitializeMaterialTypeAsset(Data::Asset<Data::AssetData> asset);
  
              // MaterialReloadNotificationBus overrides...
              void OnMaterialTypeAssetReinitialized(const Data::Asset<MaterialTypeAsset>& materialTypeAsset) override;
  
              static const char* s_debugTraceName;
  
              Data::Asset<MaterialTypeAsset> m_materialTypeAsset;
  
              //! Holds values for each material property, used to initialize Material instances.
              //! This is indexed by MaterialPropertyIndex and aligns with entries in m_materialPropertiesLayout.
              mutable AZStd::vector<MaterialPropertyValue> m_propertyValues;
  
              //! The MaterialAsset can be created in a "half-baked" state where minimal processing has been done because it does
              //! not yet have access to the MaterialTypeAsset. In that case, this list will be populated with values copied from
              //! the source .material file with little or no validation or other processing, and the m_propertyValues list will be empty.
              //! Once a MaterialTypeAsset is available, Finalize() must be called to finish processing these values into the
              //! final m_propertyValues list.
              //! Note that the content of this list will remain after finalizing in order to support hot-reload of the MaterialTypeAsset.
              //! The reason we use a vector instead of a map is to ensure inherited property values are applied in the right order;
              //! if the material has a parent, and that parent uses an older material type version with renamed properties, then 
              //! m_rawPropertyValues could be holding two values for the same property under different names. The auto-rename process
              //! can't be applied until the MaterialTypeAsset is available, so we have to keep the properties in the same order they
              //! were originally encountered.
              AZStd::vector<AZStd::pair<Name, MaterialPropertyValue>> m_rawPropertyValues;
              
              //! Tracks whether Finalize() has been called, meaning m_propertyValues is populated with data matching the material type's property layout.
              //! (This value is intentionally not serialized, it is set by the Finalize() function)
              mutable bool m_isFinalized = false;
  
              //! Tracks whether the MaterialAsset was already in a finalized state when it was loaded.
              bool m_wasPreFinalized = false;
              
              //! The materialTypeVersion this materialAsset was based off. If the versions do not match at runtime when a
              //! materialTypeAsset is loaded, automatic updates will be attempted at runtime. Note this is not needed to
              //! determine which updates to apply, but simply as an optimization to ignore the update procedure when the
              //! version numbers match. (We determine which updates to apply by simply checking the property name, and not
              //! allowing the same name to ever be used for two different properties, see MaterialTypeAssetCreator::ValidateMaterialVersion)
              uint32_t m_materialTypeVersion = 1;
          };
         
  
          class MaterialAssetHandler : public AssetHandler<MaterialAsset>
          {
              using Base = AssetHandler<MaterialAsset>;
          public:
              AZ_RTTI(MaterialAssetHandler, "{949DFEF5-6E19-4C81-8CF0-C764F474CCDD}", Base);
  
              Data::AssetHandler::LoadResult LoadAssetData(
                  const AZ::Data::Asset<AZ::Data::AssetData>& asset,
                  AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
                  const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
          };
  
      } // namespace RPI
  } // namespace AZ
  