Merge pull request #4325 from aws-lumberyard-dev/Atom/guthadam/material_component_supports_property_overrides_without_material_overrides

Atom/guthadam/material component supports property overrides without material overrides
This commit is contained in:
Guthrie Adams
2021-09-30 08:11:11 -05:00
committed by GitHub
9 changed files with 197 additions and 92 deletions
@@ -39,13 +39,24 @@ namespace AZ
//! Otherwise an attempt will be made to find or create a shared instance.
void RebuildInstance();
//! Release asset and instance references
void Release();
//! Return true if contained assets have not been loaded
bool RequiresLoading() const;
//! Applies property overrides to material instance
bool ApplyProperties();
//! Returns a string composed of the asset path.
AZStd::string ToString() const;
Data::Asset<RPI::MaterialAsset> m_materialAsset;
Data::Asset<RPI::MaterialAsset> m_defaultMaterialAsset;
Data::Instance<RPI::Material> m_materialInstance;
MaterialPropertyOverrideMap m_propertyOverrides;
RPI::MaterialModelUvOverrideMap m_matModUvOverrides;
bool m_materialInstancePreCreated = false;
};
using MaterialAssignmentMap = AZStd::unordered_map<MaterialAssignmentId, MaterialAssignment>;
@@ -71,9 +71,9 @@ namespace AZ
}
MaterialAssignment::MaterialAssignment(const AZ::Data::AssetId& materialAssetId)
: m_materialInstance()
: m_materialAsset(materialAssetId, AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid())
, m_materialInstance()
{
m_materialAsset.Create(materialAssetId);
}
MaterialAssignment::MaterialAssignment(const Data::Asset<RPI::MaterialAsset>& asset)
@@ -90,12 +90,70 @@ namespace AZ
void MaterialAssignment::RebuildInstance()
{
if (m_materialInstancePreCreated)
{
return;
}
if (m_materialAsset.IsReady())
{
m_materialInstance =
m_propertyOverrides.empty() ? RPI::Material::FindOrCreate(m_materialAsset) : RPI::Material::Create(m_materialAsset);
m_materialInstance = m_propertyOverrides.empty() ? RPI::Material::FindOrCreate(m_materialAsset) : RPI::Material::Create(m_materialAsset);
AZ_Error("MaterialAssignment", m_materialInstance, "Material instance not initialized");
}
else if (m_defaultMaterialAsset.IsReady())
{
m_materialInstance = m_propertyOverrides.empty() ? RPI::Material::FindOrCreate(m_defaultMaterialAsset) : RPI::Material::Create(m_defaultMaterialAsset);
AZ_Error("MaterialAssignment", m_materialInstance, "Material instance not initialized");
}
}
void MaterialAssignment::Release()
{
if (!m_materialInstancePreCreated)
{
m_materialInstance = nullptr;
}
m_materialAsset.Release();
m_defaultMaterialAsset.Release();
}
bool MaterialAssignment::RequiresLoading() const
{
return
!m_materialInstancePreCreated &&
!m_materialAsset.IsReady() &&
!m_materialAsset.IsLoading() &&
!m_defaultMaterialAsset.IsReady() &&
!m_defaultMaterialAsset.IsLoading();
}
bool MaterialAssignment::ApplyProperties()
{
// if there is no instance or no properties there's nothing to apply
if (!m_materialInstance || m_propertyOverrides.empty())
{
return true;
}
if (m_materialInstance->CanCompile())
{
for (const auto& propertyPair : m_propertyOverrides)
{
if (!propertyPair.second.empty())
{
const auto& materialPropertyIndex = m_materialInstance->FindPropertyIndex(propertyPair.first);
if (!materialPropertyIndex.IsNull())
{
m_materialInstance->SetPropertyValue(
materialPropertyIndex, AZ::RPI::MaterialPropertyValue::FromAny(propertyPair.second));
}
}
}
return m_materialInstance->Compile();
}
return false;
}
AZStd::string MaterialAssignment::ToString() const
@@ -182,7 +182,9 @@ namespace MaterialEditor
AZ_Error("MaterialViewportRenderer", m_shadowCatcherMaterial != nullptr, "Could not create shadow catcher material.");
AZ::Render::MaterialAssignmentMap shadowCatcherMaterials;
shadowCatcherMaterials[AZ::Render::DefaultMaterialAssignmentId].m_materialInstance = m_shadowCatcherMaterial;
auto& shadowCatcherMaterialAssignment = shadowCatcherMaterials[AZ::Render::DefaultMaterialAssignmentId];
shadowCatcherMaterialAssignment.m_materialInstance = m_shadowCatcherMaterial;
shadowCatcherMaterialAssignment.m_materialInstancePreCreated = true;
AZ::Render::MaterialComponentRequestBus::Event(m_shadowCatcherEntity->GetId(),
&AZ::Render::MaterialComponentRequestBus::Events::SetMaterialOverrides, shadowCatcherMaterials);
@@ -291,7 +293,9 @@ namespace MaterialEditor
MaterialDocumentRequestBus::EventResult(materialInstance, documentId, &MaterialDocumentRequestBus::Events::GetInstance);
AZ::Render::MaterialAssignmentMap materials;
materials[AZ::Render::DefaultMaterialAssignmentId].m_materialInstance = materialInstance;
auto& materialAssignment = materials[AZ::Render::DefaultMaterialAssignmentId];
materialAssignment.m_materialInstance = materialInstance;
materialAssignment.m_materialInstancePreCreated = true;
AZ::Render::MaterialComponentRequestBus::Event(m_modelEntity->GetId(),
&AZ::Render::MaterialComponentRequestBus::Events::SetMaterialOverrides, materials);
@@ -151,7 +151,9 @@ namespace AZ
//! Returns the list of all ModelMaterialSlot's for the model, across all LODs.
virtual RPI::ModelMaterialSlotMap GetModelMaterialSlots() const = 0;
//! Returns the available, overridable material slots and the default assigned materials
virtual MaterialAssignmentMap GetMaterialAssignments() const = 0;
virtual AZStd::unordered_set<AZ::Name> GetModelUvNames() const = 0;
};
using MaterialReceiverRequestBus = EBus<MaterialReceiverRequests>;
@@ -161,6 +163,7 @@ namespace AZ
: public ComponentBus
{
public:
//! Notification that overridable material slots are available or have changed
virtual void OnMaterialAssignmentsChanged() = 0;
};
using MaterialReceiverNotificationBus = EBus<MaterialReceiverNotifications>;
@@ -88,6 +88,12 @@ namespace AZ
AZ::Data::AssetId materialAssetId = {};
MaterialComponentRequestBus::EventResult(
materialAssetId, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialOverride, m_materialAssignmentId);
if (!materialAssetId.IsValid())
{
MaterialComponentRequestBus::EventResult(
materialAssetId, m_entityId, &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId,
m_materialAssignmentId);
}
if (!materialAssetId.IsValid())
{
@@ -728,11 +734,16 @@ namespace AZ
void MaterialPropertyInspector::UpdateUI()
{
AZ::Data::AssetId assetId;
AZ::Data::AssetId materialAssetId = {};
MaterialComponentRequestBus::EventResult(
assetId, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialOverride, m_materialAssignmentId);
materialAssetId, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialOverride, m_materialAssignmentId);
if (!materialAssetId.IsValid())
{
MaterialComponentRequestBus::EventResult(
materialAssetId, m_entityId, &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId, m_materialAssignmentId);
}
if (IsLoaded() && m_editData.m_materialAssetId == assetId)
if (IsLoaded() && m_editData.m_materialAssetId == materialAssetId)
{
LoadOverridesFromEntity();
}
@@ -118,10 +118,16 @@ namespace AZ
}
};
AZ::Data::AssetId EditorMaterialComponentSlot::GetActiveAssetId() const
{
return m_materialAsset.GetId().IsValid() ? m_materialAsset.GetId() : GetDefaultAssetId();
}
AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const
{
AZ::Data::AssetId assetId;
MaterialComponentRequestBus::EventResult(assetId, m_entityId, &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId, m_id);
MaterialComponentRequestBus::EventResult(
assetId, m_entityId, &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId, m_id);
return assetId;
}
@@ -135,7 +141,7 @@ namespace AZ
bool EditorMaterialComponentSlot::HasSourceData() const
{
// The slot only has valid source data if the source path is valid and the file has the correct extension
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_materialAsset.GetId());
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(GetActiveAssetId());
return !sourcePath.empty() && AZ::StringFunc::Path::IsExtension(sourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
}
@@ -220,7 +226,7 @@ namespace AZ
void EditorMaterialComponentSlot::OpenMaterialEditor() const
{
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_materialAsset.GetId());
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(GetActiveAssetId());
if (!sourcePath.empty() && AZ::StringFunc::Path::IsExtension(sourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{
EditorMaterialSystemComponentRequestBus::Broadcast(
@@ -236,7 +242,7 @@ namespace AZ
void EditorMaterialComponentSlot::OpenUvNameMapInspector()
{
if (m_materialAsset.GetId().IsValid())
if (GetActiveAssetId().IsValid())
{
AZStd::unordered_set<AZ::Name> modelUvNames;
MaterialReceiverRequestBus::EventResult(modelUvNames, m_entityId, &MaterialReceiverRequestBus::Events::GetModelUvNames);
@@ -252,7 +258,7 @@ namespace AZ
};
if (EditorMaterialComponentInspector::OpenInspectorDialog(
m_materialAsset.GetId(), matModUvOverrides, modelUvNames, applyMatModUvOverrideChangedCallback))
GetActiveAssetId(), matModUvOverrides, modelUvNames, applyMatModUvOverrideChangedCallback))
{
OnDataChanged();
}
@@ -274,10 +280,10 @@ namespace AZ
action->setEnabled(HasSourceData());
action = menu.addAction("Edit Material Instance...", [this]() { OpenMaterialInspector(); });
action->setEnabled(m_materialAsset.GetId().IsValid());
action->setEnabled(GetActiveAssetId().IsValid());
action = menu.addAction("Edit Material Instance UV Map...", [this]() { OpenUvNameMapInspector(); });
action->setEnabled(m_materialAsset.GetId().IsValid());
action->setEnabled(GetActiveAssetId().IsValid());
menu.addSeparator();
@@ -30,6 +30,7 @@ namespace AZ
static void Reflect(ReflectContext* context);
static bool ConvertVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
AZ::Data::AssetId GetActiveAssetId() const;
AZ::Data::AssetId GetDefaultAssetId() const;
AZStd::string GetLabel() const;
bool HasSourceData() const;
@@ -111,12 +111,14 @@ namespace AZ
m_queuedMaterialUpdateNotification = false;
MaterialComponentRequestBus::Handler::BusConnect(m_entityId);
MaterialReceiverNotificationBus::Handler::BusConnect(m_entityId);
LoadMaterials();
}
void MaterialComponentController::Deactivate()
{
MaterialComponentRequestBus::Handler::BusDisconnect();
MaterialReceiverNotificationBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
ReleaseMaterials();
@@ -146,56 +148,26 @@ namespace AZ
void MaterialComponentController::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
AZStd::unordered_set<MaterialAssignmentId> propertyOverrides;
AZStd::swap(m_queuedPropertyOverrides, propertyOverrides);
AZStd::unordered_set<MaterialAssignmentId> materialsWithDirtyProperties;
AZStd::swap(m_materialsWithDirtyProperties, materialsWithDirtyProperties);
// Iterate through all MaterialAssignmentId's that have property overrides and attempt to apply them
// if material instance is already compiling, delay application of property overrides until next frame
for (const auto& materialAssignmentId : propertyOverrides)
for (const auto& materialAssignmentId : materialsWithDirtyProperties)
{
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
if (materialIt != m_configuration.m_materials.end())
{
//Skip materials that do not exist in the map
continue;
}
auto materialInstance = materialIt->second.m_materialInstance;
if (!materialInstance)
{
//Skip materials with an invalid instances
continue;
}
if (!materialInstance->CanCompile())
{
//If a material cannot currently be compiled then it must be queued again
m_queuedPropertyOverrides.emplace(materialAssignmentId);
continue;
}
const auto& propertyOverrides2 = materialIt->second.m_propertyOverrides;
for (auto& propertyPair : propertyOverrides2)
{
if (propertyPair.second.empty())
if (!materialIt->second.ApplyProperties())
{
continue;
// If a material cannot currently be compiled then it must be queued again
m_materialsWithDirtyProperties.emplace(materialAssignmentId);
}
const auto& materialPropertyIndex = materialInstance->FindPropertyIndex(propertyPair.first);
if (materialPropertyIndex.IsNull())
{
continue;
}
materialInstance->SetPropertyValue(materialPropertyIndex, AZ::RPI::MaterialPropertyValue::FromAny(propertyPair.second));
}
materialInstance->Compile();
}
// Only disconnect from tick bus and send notification after all pending properties have been applied
if (m_queuedPropertyOverrides.empty())
if (m_materialsWithDirtyProperties.empty())
{
if (m_queuedMaterialUpdateNotification)
{
@@ -213,15 +185,35 @@ namespace AZ
Data::AssetBus::MultiHandler::BusDisconnect();
bool anyQueued = false;
for (auto& materialPair : m_configuration.m_materials)
auto queueAsset = [&anyQueued, this](AZ::Data::Asset<AZ::RPI::MaterialAsset>& materialAsset) -> bool
{
auto& materialAsset = materialPair.second.m_materialAsset;
if (materialAsset.GetId().IsValid() && !Data::AssetBus::MultiHandler::BusIsConnectedId(materialAsset.GetId()))
if (materialAsset.GetId().IsValid() && !this->Data::AssetBus::MultiHandler::BusIsConnectedId(materialAsset.GetId()))
{
anyQueued = true;
materialAsset.QueueLoad();
Data::AssetBus::MultiHandler::BusConnect(materialAsset.GetId());
this->Data::AssetBus::MultiHandler::BusConnect(materialAsset.GetId());
return true;
}
return false;
};
for (auto& materialPair : m_configuration.m_materials)
{
if (materialPair.second.m_materialInstancePreCreated)
{
continue;
}
materialPair.second.m_defaultMaterialAsset = {};
if (!queueAsset(materialPair.second.m_materialAsset))
{
// Only assign and load the default material if there was no material override and there are propoerties to apply
if (!materialPair.second.m_propertyOverrides.empty() || !materialPair.second.m_matModUvOverrides.empty())
{
materialPair.second.m_defaultMaterialAsset = AZ::Data::Asset<AZ::RPI::MaterialAsset>(
GetDefaultMaterialAssetId(materialPair.first), AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid());
queueAsset(materialPair.second.m_defaultMaterialAsset);
}
}
}
@@ -234,10 +226,8 @@ namespace AZ
void MaterialComponentController::InitializeMaterialInstance(const Data::Asset<Data::AssetData>& asset)
{
bool allReady = true;
for (auto& materialPair : m_configuration.m_materials)
auto updateAsset = [&](AZ::Data::Asset<AZ::RPI::MaterialAsset>& materialAsset)
{
auto& materialAsset = materialPair.second.m_materialAsset;
if (materialAsset.GetId() == asset.GetId())
{
materialAsset = asset;
@@ -247,6 +237,12 @@ namespace AZ
{
allReady = false;
}
};
for (auto& materialPair : m_configuration.m_materials)
{
updateAsset(materialPair.second.m_materialAsset);
updateAsset(materialPair.second.m_defaultMaterialAsset);
}
if (allReady)
@@ -269,11 +265,7 @@ namespace AZ
for (auto& materialPair : m_configuration.m_materials)
{
if (materialPair.second.m_materialAsset.GetId().IsValid())
{
materialPair.second.m_materialAsset.Release();
materialPair.second.m_materialInstance = nullptr;
}
materialPair.second.Release();
}
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials);
@@ -441,12 +433,7 @@ namespace AZ
AZ::Data::AssetId MaterialComponentController::GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const
{
auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
{
return {};
}
return materialIt->second.m_materialAsset.GetId();
return materialIt != m_configuration.m_materials.end() ? materialIt->second.m_materialAsset.GetId() : AZ::Data::AssetId();
}
void MaterialComponentController::ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId)
@@ -460,18 +447,21 @@ namespace AZ
void MaterialComponentController::SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value)
{
auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
const bool wasEmpty = materialAssignment.m_propertyOverrides.empty();
materialAssignment.m_propertyOverrides[AZ::Name(propertyName)] = value;
// When applying property overrides for the first time, new instance needs to be created in case the current instance is already used somewhere else to keep overrides local
if (materialAssignment.m_propertyOverrides.empty())
if (materialAssignment.RequiresLoading())
{
materialAssignment.m_propertyOverrides[AZ::Name(propertyName)] = value;
materialAssignment.RebuildInstance();
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialInstanceCreated, materialAssignment);
QueueMaterialUpdateNotification();
LoadMaterials();
return;
}
else
if (wasEmpty != materialAssignment.m_propertyOverrides.empty())
{
materialAssignment.m_propertyOverrides[AZ::Name(propertyName)] = value;
materialAssignment.RebuildInstance();
MaterialComponentNotificationBus::Event(
m_entityId, &MaterialComponentNotifications::OnMaterialInstanceCreated, materialAssignment);
QueueMaterialUpdateNotification();
}
QueuePropertyChanges(materialAssignmentId);
@@ -704,6 +694,12 @@ namespace AZ
const bool wasEmpty = materialAssignment.m_propertyOverrides.empty();
materialAssignment.m_propertyOverrides = propertyOverrides;
if (materialAssignment.RequiresLoading())
{
LoadMaterials();
return;
}
if (wasEmpty != materialAssignment.m_propertyOverrides.empty())
{
materialAssignment.RebuildInstance();
@@ -713,14 +709,11 @@ namespace AZ
QueuePropertyChanges(materialAssignmentId);
}
MaterialPropertyOverrideMap MaterialComponentController::GetPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) const
MaterialPropertyOverrideMap MaterialComponentController::GetPropertyOverrides(
const MaterialAssignmentId& materialAssignmentId) const
{
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
{
return {};
}
return materialIt->second.m_propertyOverrides;
return materialIt != m_configuration.m_materials.end() ? materialIt->second.m_propertyOverrides : MaterialPropertyOverrideMap();
}
void MaterialComponentController::SetModelUvOverrides(
@@ -730,6 +723,12 @@ namespace AZ
const bool wasEmpty = materialAssignment.m_matModUvOverrides.empty();
materialAssignment.m_matModUvOverrides = modelUvOverrides;
if (materialAssignment.RequiresLoading())
{
LoadMaterials();
return;
}
if (wasEmpty != materialAssignment.m_matModUvOverrides.empty())
{
materialAssignment.RebuildInstance();
@@ -743,16 +742,24 @@ namespace AZ
const MaterialAssignmentId& materialAssignmentId) const
{
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
return materialIt != m_configuration.m_materials.end() ? materialIt->second.m_matModUvOverrides : AZ::RPI::MaterialModelUvOverrideMap();
}
void MaterialComponentController::OnMaterialAssignmentsChanged()
{
for (const auto& materialPair : m_configuration.m_materials)
{
return {};
if (materialPair.second.RequiresLoading())
{
LoadMaterials();
return;
}
}
return materialIt->second.m_matModUvOverrides;
}
void MaterialComponentController::QueuePropertyChanges(const MaterialAssignmentId& materialAssignmentId)
{
m_queuedPropertyOverrides.emplace(materialAssignmentId);
m_materialsWithDirtyProperties.emplace(materialAssignmentId);
if (!TickBus::Handler::BusIsConnected())
{
TickBus::Handler::BusConnect();
@@ -22,6 +22,7 @@ namespace AZ
//! to provide material overrides on a per-entity basis.
class MaterialComponentController final
: MaterialComponentRequestBus::Handler
, MaterialReceiverNotificationBus::Handler
, Data::AssetBus::MultiHandler
, TickBus::Handler
{
@@ -100,6 +101,9 @@ namespace AZ
const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides) override;
AZ::RPI::MaterialModelUvOverrideMap GetModelUvOverrides(const MaterialAssignmentId& materialAssignmentId) const override;
//! MaterialReceiverNotificationBus::Handler overrides...
void OnMaterialAssignmentsChanged() override;
private:
AZ_DISABLE_COPY(MaterialComponentController);
@@ -121,7 +125,7 @@ namespace AZ
EntityId m_entityId;
MaterialComponentConfig m_configuration;
AZStd::unordered_set<MaterialAssignmentId> m_queuedPropertyOverrides;
AZStd::unordered_set<MaterialAssignmentId> m_materialsWithDirtyProperties;
bool m_queuedMaterialUpdateNotification = false;
};
} // namespace Render