Files
UnrealEngine/Engine/Source/Runtime/MovieSceneTracks/Public/Systems/MovieSceneMaterialSystem.h
2025-05-18 13:04:45 +08:00

488 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "EntitySystem/MovieSceneEntitySystem.h"
#include "EntitySystem/MovieScenePreAnimatedStateSystem.h"
#include "EntitySystem/MovieSceneEntityMutations.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "EntitySystem/BuiltInComponentTypes.h"
#include "EntitySystem/MovieSceneEntitySystemRunner.h"
#include "EntitySystem/MovieSceneComponentTypeInfo.h"
#include "EntitySystem/MovieSceneCachedEntityFilterResult.h"
#include "EntitySystem/MovieSceneEntityGroupingSystem.h"
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedObjectStorage.h"
#include "MovieSceneTracksComponentTypes.h"
#include "UObject/SoftObjectPtr.h"
#include "MovieSceneMaterialSystem.generated.h"
DECLARE_CYCLE_STAT_EXTERN(TEXT("Reinitialize Bound Materials"), MovieSceneEval_ReinitializeBoundMaterials, STATGROUP_MovieSceneECS, MOVIESCENETRACKS_API);
USTRUCT()
struct FMovieScenePreAnimatedMaterialParameters
{
GENERATED_BODY()
MOVIESCENETRACKS_API UMaterialInterface* GetMaterial() const;
MOVIESCENETRACKS_API void SetMaterial(UMaterialInterface* InMaterial);
private:
/** Strong ptr to the previously assigned material interface (used when Sequencer.UseSoftObjectPtrsForPreAnimatedMaterial is false) */
UPROPERTY()
TObjectPtr<UMaterialInterface> PreviousMaterial;
/** Soft ptr to the previously assigned material interface (used when Sequencer.UseSoftObjectPtrsForPreAnimatedMaterial is true) */
UPROPERTY()
TSoftObjectPtr<UMaterialInterface> SoftPreviousMaterial;
};
namespace UE::MovieScene
{
template<typename AccessorType, typename... RequiredComponents>
struct TPreAnimatedMaterialTraits : FBoundObjectPreAnimatedStateTraits
{
using KeyType = typename AccessorType::KeyType;
using StorageType = FMovieScenePreAnimatedMaterialParameters;
static_assert(THasAddReferencedObjectForComponent<StorageType>::Value, "StorageType is not correctly exposed to the reference graph!");
static FMovieScenePreAnimatedMaterialParameters CachePreAnimatedValue(typename TCallTraits<RequiredComponents>::ParamType... InRequiredComponents)
{
AccessorType Accessor{ InRequiredComponents... };
FMovieScenePreAnimatedMaterialParameters Parameters;
if (Accessor)
{
Parameters.SetMaterial(Accessor.GetMaterial());
}
return Parameters;
}
static void RestorePreAnimatedValue(const KeyType& InKey, const FMovieScenePreAnimatedMaterialParameters& PreAnimatedValue, const FRestoreStateParams& Params)
{
AccessorType Accessor{ InKey };
if (Accessor)
{
if (UMaterialInterface* PreviousMaterial = PreAnimatedValue.GetMaterial())
{
Accessor.SetMaterial(PreAnimatedValue.GetMaterial());
}
}
}
};
template<typename AccessorType, typename... RequiredComponents>
using TPreAnimatedMaterialParameterTraits = TPreAnimatedMaterialTraits<AccessorType, RequiredComponents...>;
template<typename AccessorType, typename... RequiredComponents>
class TMovieSceneMaterialSystem
{
public:
using MaterialSwitcherStorageType = TPreAnimatedStateStorage<TPreAnimatedMaterialTraits<AccessorType, RequiredComponents...>>;
using MaterialParameterStorageType = TPreAnimatedStateStorage<TPreAnimatedMaterialParameterTraits<AccessorType, RequiredComponents...>>;
TSharedPtr<MaterialSwitcherStorageType> MaterialSwitcherStorage;
TSharedPtr<MaterialParameterStorageType> MaterialParameterStorage;
void OnLink(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID<RequiredComponents>... InRequiredComponents);
void OnUnlink(UMovieSceneEntitySystemLinker* Linker);
void OnRun(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID<RequiredComponents>... InRequiredComponents, FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents);
void SavePreAnimatedState(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID<RequiredComponents>... InRequiredComponents, const IMovieScenePreAnimatedStateSystemInterface::FPreAnimationParameters& InParameters);
void OnPostSpawn(UMovieSceneEntitySystemLinker* InLinker, TComponentTypeID<RequiredComponents>... InRequiredComponents);
protected:
struct FMaterialGroupingPolicy
{
using GroupKeyType = TTuple<RequiredComponents..., FMaterialParameterInfo>;
void InitializeGroupKeys(
TEntityGroupingHandlerBase<FMaterialGroupingPolicy>& Handler,
FEntityGroupBuilder* Builder,
FEntityAllocationIteratorItem Item,
FReadEntityIDs EntityIDs,
TWrite<FEntityGroupID> GroupIDs,
TRead<RequiredComponents>... Components)
{
const FComponentMask& AllocationType = Item.GetAllocationType();
FMovieSceneTracksComponentTypes* TracksComponents = FMovieSceneTracksComponentTypes::Get();
TComponentTypeID<FName> ParameterName;
if (AllocationType.Contains(TracksComponents->ScalarParameterName))
{
ParameterName = TracksComponents->ScalarParameterName;
}
else if (AllocationType.Contains(TracksComponents->VectorParameterName))
{
ParameterName = TracksComponents->VectorParameterName;
}
else if (AllocationType.Contains(TracksComponents->ColorParameterName))
{
ParameterName = TracksComponents->ColorParameterName;
}
TComponentTypeID<FMaterialParameterInfo> ParameterInfo;
if (AllocationType.Contains(TracksComponents->ScalarMaterialParameterInfo))
{
ParameterInfo = TracksComponents->ScalarMaterialParameterInfo;
}
else if (AllocationType.Contains(TracksComponents->VectorMaterialParameterInfo))
{
ParameterInfo = TracksComponents->VectorMaterialParameterInfo;
}
else if (AllocationType.Contains(TracksComponents->ColorMaterialParameterInfo))
{
ParameterInfo = TracksComponents->ColorMaterialParameterInfo;
}
const FEntityAllocation* Allocation = Item.GetAllocation();
const int32 Num = Allocation->Num();
TReadOptional<FName> ParameterNames = ParameterName ? Allocation->TryReadComponents(ParameterName) : TReadOptional<FName>();
TReadOptional<FMaterialParameterInfo> ParameterInfos = ParameterInfo ? Allocation->TryReadComponents(ParameterInfo) : TReadOptional<FMaterialParameterInfo>();
for (int32 Index = 0; Index < Num; ++Index)
{
GroupKeyType Key = ParameterInfos
? MakeTuple(Components[Index]..., ParameterInfos[Index])
: ParameterNames
? MakeTuple(Components[Index]..., FMaterialParameterInfo(ParameterNames[Index]))
: MakeTuple(Components[Index]..., FMaterialParameterInfo());
const int32 NewGroupIndex = Handler.GetOrAllocateGroupIndex(Key, Builder);
FEntityGroupID NewGroupID = Builder->MakeGroupID(NewGroupIndex);
Builder->AddEntityToGroup(EntityIDs[Index], NewGroupID);
// Write out the group ID component
GroupIDs[Index] = NewGroupID;
}
}
#if WITH_EDITOR
bool OnObjectsReplaced(GroupKeyType& InOutKey, const TMap<UObject*, UObject*>& ReplacementMap)
{
return false;
}
#endif
};
UE::MovieScene::FEntityGroupingPolicyKey GroupingKey;
FEntityComponentFilter MaterialSwitcherFilter;
FEntityComponentFilter MaterialParameterFilter;
FCachedEntityFilterResult_Allocations ReinitializeBoundMaterials;
};
template<typename AccessorType, typename... RequiredComponents>
struct TApplyMaterialSwitchers
{
static void ForEachEntity(typename TCallTraits<RequiredComponents>::ParamType... Inputs, const FObjectComponent& ObjectResult)
{
// ObjectResult must be a material
UMaterialInterface* NewMaterial = Cast<UMaterialInterface>(ObjectResult.GetObject());
AccessorType Accessor(Inputs...);
if (!Accessor)
{
return;
}
UMaterialInterface* ExistingMaterial = Accessor.GetMaterial();
UMaterialInstanceDynamic* ExistingMID = Cast<UMaterialInstanceDynamic>(ExistingMaterial);
if (ExistingMID && ExistingMID->Parent && ExistingMID->Parent == NewMaterial)
{
// Do not re-assign materials when a dynamic instance is already assigned with the same parent (since that's basically the same material, just with animated parameters)
// This is required for supporting material switchers alongside parameter tracks
return;
}
Accessor.SetMaterial(NewMaterial);
}
};
template<typename AccessorType, typename... RequiredComponents>
struct TInitializeBoundMaterials
{
static bool InitializeBoundMaterial(typename TCallTraits<RequiredComponents>::ParamType... Inputs, FObjectComponent& OutDynamicMaterial)
{
AccessorType Accessor(Inputs...);
if (!Accessor)
{
OutDynamicMaterial = FObjectComponent::Null();
return false;
}
UMaterialInterface* ExistingMaterial = Accessor.GetMaterial();
if (!ExistingMaterial)
{
// If the object was not previously explicitly null, make it so
if (OutDynamicMaterial != FObjectComponent::Null())
{
OutDynamicMaterial = FObjectComponent::Null();
return true;
}
return false;
}
if (UMaterialInstanceDynamic* MID = Cast<UMaterialInstanceDynamic>(ExistingMaterial))
{
if (OutDynamicMaterial != MID)
{
OutDynamicMaterial = FObjectComponent::Weak(MID);
return true;
}
return false;
}
UMaterialInstanceDynamic* CurrentMID = Cast<UMaterialInstanceDynamic>(OutDynamicMaterial.GetObject());
if (CurrentMID && CurrentMID->Parent == ExistingMaterial)
{
Accessor.SetMaterial(CurrentMID);
return false;
}
UMaterialInstanceDynamic* NewMaterial = Accessor.CreateDynamicMaterial(ExistingMaterial);
OutDynamicMaterial = FObjectComponent::Weak(NewMaterial);
Accessor.SetMaterial(NewMaterial);
return true;
}
static void ForEachEntity(typename TCallTraits<RequiredComponents>::ParamType... Inputs, FObjectComponent& OutDynamicMaterial)
{
InitializeBoundMaterial(Inputs..., OutDynamicMaterial);
}
};
template<typename AccessorType, typename... RequiredComponents>
struct TReinitializeBoundMaterials
{
UMovieSceneEntitySystemLinker* Linker;
TArray<FMovieSceneEntityID> ReboundMaterials;
TReinitializeBoundMaterials(UMovieSceneEntitySystemLinker* InLinker)
: Linker(InLinker)
{}
void ForEachAllocation(int32 Num, const FMovieSceneEntityID* EntityIDs, const RequiredComponents*... Inputs, FObjectComponent* Objects)
{
for (int32 Index = 0; Index < Num; ++Index)
{
ForEachEntity(EntityIDs[Index], Inputs[Index]..., Objects[Index]);
}
}
void ForEachEntity(FMovieSceneEntityID EntityID, typename TCallTraits<RequiredComponents>::ParamType... Inputs, FObjectComponent& OutDynamicMaterial)
{
if (TInitializeBoundMaterials<AccessorType, RequiredComponents...>::InitializeBoundMaterial(Inputs..., OutDynamicMaterial))
{
ReboundMaterials.Add(EntityID);
}
}
void PostTask()
{
FMovieSceneTracksComponentTypes* TracksComponents = FMovieSceneTracksComponentTypes::Get();
for (FMovieSceneEntityID EntityID : ReboundMaterials)
{
Linker->EntityManager.AddComponent(EntityID, TracksComponents->Tags.BoundMaterialChanged);
}
}
};
template<typename...>
struct TAddBoundMaterialMutationImpl;
template<typename AccessorType, typename... RequiredComponents, int... Indices>
struct TAddBoundMaterialMutationImpl<AccessorType, TIntegerSequence<int, Indices...>, RequiredComponents...> : IMovieSceneEntityMutation
{
TAddBoundMaterialMutationImpl(TComponentTypeID<RequiredComponents>... InRequiredComponents)
: ComponentTypes(InRequiredComponents...)
{
BuiltInComponents = FBuiltInComponentTypes::Get();
TracksComponents = FMovieSceneTracksComponentTypes::Get();
}
virtual void CreateMutation(FEntityManager* EntityManager, FComponentMask* InOutEntityComponentTypes) const override
{
InOutEntityComponentTypes->Set(TracksComponents->BoundMaterial);
}
virtual void InitializeAllocation(FEntityAllocation* Allocation, const FComponentMask& AllocationType) const
{
TComponentWriter<FObjectComponent> BoundMaterials = Allocation->WriteComponents(TracksComponents->BoundMaterial, FEntityAllocationWriteContext::NewAllocation());
InitializeAllocation(Allocation, BoundMaterials, Allocation->ReadComponents(ComponentTypes.template Get<Indices>())...);
}
void InitializeAllocation(FEntityAllocation* Allocation, FObjectComponent* OutBoundMaterials, const RequiredComponents*... InRequiredComponents) const
{
const int32 Num = Allocation->Num();
for (int32 Index = 0; Index < Num; ++Index)
{
OutBoundMaterials[Index] = FObjectComponent::Null();
}
}
private:
FBuiltInComponentTypes* BuiltInComponents;
FMovieSceneTracksComponentTypes* TracksComponents;
TTuple<TComponentTypeID<RequiredComponents>...> ComponentTypes;
};
template<typename AccessorType, typename... RequiredComponents>
void TMovieSceneMaterialSystem<AccessorType, RequiredComponents...>::OnLink(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID<RequiredComponents>... InRequiredComponents)
{
using namespace UE::MovieScene;
FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
FMovieSceneTracksComponentTypes* TracksComponents = FMovieSceneTracksComponentTypes::Get();
// Define a grouping for these materials. This will make hierarchical bias work.
UMovieSceneEntityGroupingSystem* GroupingSystem = Linker->LinkSystem<UMovieSceneEntityGroupingSystem>();
GroupingKey = GroupingSystem->AddGrouping(FMaterialGroupingPolicy(), InRequiredComponents...);
MaterialSwitcherFilter.Reset();
MaterialSwitcherFilter.All({ InRequiredComponents..., BuiltInComponents->ObjectResult });
// Currently the only supported entities that we initialize are ones that contain Scalar, Vector or Color parameters
// Imported entities are implicitly excluded by way of filtering by BoundObject, which do not exist on imported entities
MaterialParameterFilter.Reset();
MaterialParameterFilter.All({ InRequiredComponents... });
MaterialParameterFilter.Any({ TracksComponents->ScalarParameterName, TracksComponents->ColorParameterName, TracksComponents->VectorParameterName, // Old style parameter types for deprecated UMovieSceneParameterSections
TracksComponents->ScalarMaterialParameterInfo, TracksComponents->ColorMaterialParameterInfo, TracksComponents->VectorMaterialParameterInfo }); // New style parameter types
Linker->Events.PostSpawnEvent.AddRaw(this, &TMovieSceneMaterialSystem<AccessorType, RequiredComponents...>::OnPostSpawn, InRequiredComponents...);
ReinitializeBoundMaterials.Filter.All({ TracksComponents->BoundMaterial, InRequiredComponents... });
ReinitializeBoundMaterials.Filter.None({ BuiltInComponents->Tags.NeedsUnlink });
}
template<typename AccessorType, typename... RequiredComponents>
void TMovieSceneMaterialSystem<AccessorType, RequiredComponents...>::OnUnlink(UMovieSceneEntitySystemLinker* Linker)
{
UMovieSceneEntityGroupingSystem* GroupingSystem = Linker->FindSystem<UMovieSceneEntityGroupingSystem>();
if (ensure(GroupingSystem))
{
GroupingSystem->RemoveGrouping(GroupingKey);
}
GroupingKey = FEntityGroupingPolicyKey();
Linker->Events.PostSpawnEvent.RemoveAll(this);
}
template<typename AccessorType, typename... RequiredComponents>
void TMovieSceneMaterialSystem<AccessorType, RequiredComponents...>::OnPostSpawn(UMovieSceneEntitySystemLinker* InLinker, TComponentTypeID<RequiredComponents>... InRequiredComponents)
{
using namespace UE::MovieScene;
SCOPE_CYCLE_COUNTER(MovieSceneEval_ReinitializeBoundMaterials)
FMovieSceneTracksComponentTypes* TracksComponents = FMovieSceneTracksComponentTypes::Get();
FEntityAllocationWriteContext WriteContext(InLinker->EntityManager);
TReinitializeBoundMaterials<AccessorType, RequiredComponents...> ReinitializeBoundMaterialsTask(InLinker);
// Reinitialize bound dynamic materials, adding NeedsLink during PostTask to any that changed
// This will cause the instantiation phase to be re-run for these entities (and any other new or expired ones)
for (FEntityAllocation* Allocation : ReinitializeBoundMaterials.GetMatchingAllocations(InLinker->EntityManager))
{
FEntityAllocationMutexGuard LockGuard(Allocation, EComponentHeaderLockMode::LockFree);
const int32 Num = Allocation->Num();
ReinitializeBoundMaterialsTask.ForEachAllocation(Num, Allocation->GetRawEntityIDs(), Allocation->ReadComponents(InRequiredComponents)..., Allocation->WriteComponents(TracksComponents->BoundMaterial, WriteContext));
}
ReinitializeBoundMaterialsTask.PostTask();
}
template<typename AccessorType, typename... RequiredComponents>
void TMovieSceneMaterialSystem<AccessorType, RequiredComponents...>::OnRun(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID<RequiredComponents>... InRequiredComponents, FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents)
{
using namespace UE::MovieScene;
FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
FMovieSceneTracksComponentTypes* TracksComponents = FMovieSceneTracksComponentTypes::Get();
TSharedRef<FMovieSceneEntitySystemRunner> Runner = Linker->GetRunner();
ESystemPhase CurrentPhase = Runner->GetCurrentPhase();
if (CurrentPhase == ESystemPhase::Instantiation)
{
// --------------------------------------------------------------------------------------
// Apply material switchers
TApplyMaterialSwitchers<AccessorType, RequiredComponents...> ApplyMaterialSwitchers;
FEntityTaskBuilder()
.ReadAllOf(InRequiredComponents...)
.Read(BuiltInComponents->ObjectResult)
.FilterAll({ BuiltInComponents->Tags.NeedsLink })
.FilterNone({ BuiltInComponents->Tags.Ignored })
.RunInline_PerEntity(&Linker->EntityManager, ApplyMaterialSwitchers);
// --------------------------------------------------------------------------------------
// Add bound materials for any NeedsLink entities that have material parameters
FEntityComponentFilter Filter(MaterialParameterFilter);
Filter.All({ BuiltInComponents->Tags.NeedsLink });
Filter.None({ BuiltInComponents->Tags.ImportedEntity });
using MutationType = TAddBoundMaterialMutationImpl<AccessorType, TMakeIntegerSequence<int, sizeof...(RequiredComponents)>, RequiredComponents...>;
Linker->EntityManager.MutateAll(Filter, MutationType(InRequiredComponents...));
// --------------------------------------------------------------------------------------
// (Re)initialize bound materials for any NeedsLink materials
TReinitializeBoundMaterials<AccessorType, RequiredComponents...> ReinitializeBoundMaterialsTask(Linker);
FEntityTaskBuilder()
.ReadEntityIDs()
.ReadAllOf(InRequiredComponents...)
.Write(TracksComponents->BoundMaterial)
.FilterAll({ BuiltInComponents->Tags.NeedsLink })
.FilterNone({ BuiltInComponents->Tags.Ignored, BuiltInComponents->Tags.NeedsUnlink })
.RunInline_PerEntity(&Linker->EntityManager, ReinitializeBoundMaterialsTask);
}
}
template<typename AccessorType, typename... RequiredComponents>
void TMovieSceneMaterialSystem<AccessorType, RequiredComponents...>::SavePreAnimatedState(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID<RequiredComponents>... InRequiredComponents, const IMovieScenePreAnimatedStateSystemInterface::FPreAnimationParameters& InParameters)
{
using namespace UE::MovieScene;
// If we have material results to apply save those as well
if (Linker->EntityManager.Contains(MaterialSwitcherFilter))
{
TPreAnimatedStateTaskParams<RequiredComponents...> Params;
Params.AdditionalFilter = MaterialSwitcherFilter;
MaterialSwitcherStorage->BeginTrackingAndCachePreAnimatedValuesTask(Linker, Params, InRequiredComponents...);
}
// If we have bound materials to resolve save the current material
if (Linker->EntityManager.Contains(MaterialParameterFilter))
{
TPreAnimatedStateTaskParams<RequiredComponents...> Params;
Params.AdditionalFilter = MaterialParameterFilter;
MaterialParameterStorage->BeginTrackingAndCachePreAnimatedValuesTask(Linker, Params, InRequiredComponents...);
}
}
} // namespace UE::MovieScene