Files
UnrealEngine/Engine/Plugins/Mutable/Source/CustomizableObject/Private/MuCO/CustomizableInstanceLODManagement.cpp
2025-05-18 13:04:45 +08:00

513 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuCO/CustomizableInstanceLODManagement.h"
#include "MuCO/CustomizableObjectInstanceUsagePrivate.h"
#include "Camera/CameraActor.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/SkinnedAssetCommon.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "MuCO/CustomizableObject.h"
#include "MuCO/CustomizableObjectInstance.h"
#include "MuCO/CustomizableObjectSystem.h"
#include "MuCO/UnrealPortabilityHelpers.h"
#include "MuCO/CustomizableObjectInstanceUsage.h"
#include "UObject/UObjectIterator.h"
#include "Components/SkeletalMeshComponent.h"
#include "MuCO/CustomizableObjectInstancePrivate.h"
#include "MuCO/CustomizableObjectPrivate.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CustomizableInstanceLODManagement)
#if WITH_EDITOR
#include "LevelEditorViewport.h"
#endif
static TAutoConsoleVariable<int32> CVarNumGeneratedInstancesLimit(
TEXT("b.NumGeneratedInstancesLimit"),
0,
TEXT("If different than 0, limit the number of mutable instances with full LODs to have at a given time."),
ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarNumGeneratedInstancesLimitLOD1(
TEXT("b.NumGeneratedInstancesLimitLOD1"),
0,
TEXT("If different than 0, limit the number of mutable instances with LOD 1 to have at a given time."),
ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarNumGeneratedInstancesLimitLOD2(
TEXT("b.NumGeneratedInstancesLimitLOD2"),
0,
TEXT("If different than 0, limit the number of mutable instances with LOD 2 to have at a given time."),
ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarDistanceForFixedLOD2(
TEXT("b.DistanceForFixedLOD2"),
50000,
TEXT("If NumGeneratedInstancesLimit is different than 0, sets the distance at which the system will fix the LOD of an instance to the lowest res one (LOD2) to prevent unnecessary LOD changes and memory consumption"),
ECVF_Scalability);
static TAutoConsoleVariable<bool> CVarOnlyUpdateCloseCustomizableObjects(
TEXT("b.OnlyUpdateCloseCustomizableObjects"),
false,
TEXT("If true, only CustomizableObjects within a predefined distance to the view centers will be generated"),
ECVF_Scalability);
#if WITH_EDITOR
void UCustomizableInstanceLODManagementBase::EditorUpdateComponent(UCustomizableObjectInstanceUsage* InstanceUsage)
{
}
#endif
UCustomizableInstanceLODManagement::UCustomizableInstanceLODManagement() : UCustomizableInstanceLODManagementBase()
{
CloseCustomizableObjectsDist = 2000.f;
}
UCustomizableInstanceLODManagement::~UCustomizableInstanceLODManagement()
{
}
// Used to manually update distances used in "OnlyUpdateCloseCustomizableObjects" system. If OnlyForInstance is null, all instances have their distance updated
// ViewCenter is the origin where the distances will be measured from.
void UpdatePawnToInstancesDistances(const class UCustomizableObjectInstance* OnlyForInstance, const TWeakObjectPtr<const AActor> ViewCenter)
{
for (TObjectIterator<UCustomizableObjectInstanceUsage> CustomizableObjectInstanceUsage; CustomizableObjectInstanceUsage; ++CustomizableObjectInstanceUsage)
{
#if WITH_EDITOR
if (IsValid(*CustomizableObjectInstanceUsage) && CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer))
{
continue;
}
#endif
if (IsValid(*CustomizableObjectInstanceUsage) && (OnlyForInstance == nullptr || CustomizableObjectInstanceUsage->GetCustomizableObjectInstance() == OnlyForInstance))
{
CustomizableObjectInstanceUsage->GetPrivate()->UpdateDistFromComponentToPlayer(ViewCenter.IsValid() ? ViewCenter.Get() : nullptr, OnlyForInstance != nullptr);
}
}
}
#if WITH_EDITOR
// Used to manually update instances in the level editor
void UpdateCameraToInstancesDistance(const FVector CameraPosition)
{
for (TObjectIterator<UCustomizableObjectInstanceUsage> CustomizableObjectInstanceUsage; CustomizableObjectInstanceUsage; ++CustomizableObjectInstanceUsage)
{
#if WITH_EDITOR
if (IsValid(*CustomizableObjectInstanceUsage) && CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer))
{
continue;
}
#endif
if (IsValid(*CustomizableObjectInstanceUsage) && !CustomizableObjectInstanceUsage->IsTemplate())
{
CustomizableObjectInstanceUsage->GetPrivate()->UpdateDistFromComponentToLevelEditorCamera(CameraPosition);
}
}
}
#endif
void UCustomizableInstanceLODManagement::UpdateInstanceDistsAndLODs(FMutableInstanceUpdateMap& InOutRequestedUpdates)
{
int32 NumGeneratedInstancesLimitLODs = GetNumGeneratedInstancesLimitFullLODs();
if (NumGeneratedInstancesLimitLODs > 0 || (IsOnlyUpdateCloseCustomizableObjectsEnabled() && IsOnlyGenerateRequestedLODLevelsEnabled()))
{
UCustomizableObjectSystem* CustomizableObjectSystem = UCustomizableObjectSystem::GetInstance();
if (ViewCenters.Num() == 0) // Just use the first pawn
{
UCustomizableObjectInstanceUsage* FirstCustomizableObjectInstanceUsage = nullptr;
#if WITH_EDITOR
bool bLevelEditorInstancesUpdated = false;
#endif
for (TObjectIterator<UCustomizableObjectInstanceUsage> CustomizableObjectInstanceUsage; CustomizableObjectInstanceUsage; ++CustomizableObjectInstanceUsage)
{
#if WITH_EDITOR
if (IsValid(*CustomizableObjectInstanceUsage) && CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer))
{
continue;
}
#endif
if (!IsValid(*CustomizableObjectInstanceUsage))
{
continue;
}
USkeletalMeshComponent* Parent = Cast<USkeletalMeshComponent>(CustomizableObjectInstanceUsage->GetAttachParent());
if (!CustomizableObjectInstanceUsage->IsTemplate())
{
UWorld* LocalWorld = Parent ? Parent->GetWorld() : nullptr;
APlayerController* Controller = LocalWorld ? LocalWorld->GetFirstPlayerController() : nullptr;
TWeakObjectPtr<const AActor> ViewCenter = Controller ? TWeakObjectPtr<const AActor>(Controller->GetPawn()) : nullptr;
if (ViewCenter.IsValid())
{
UpdatePawnToInstancesDistances(nullptr, ViewCenter);
break;
}
#if WITH_EDITOR
else if (Parent && Parent->GetWorld())
{
EWorldType::Type WorldType = Parent->GetWorld()->WorldType;
// Level Editor Instances (non PIE)
if (!bLevelEditorInstancesUpdated && WorldType == EWorldType::Editor)
{
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC && LevelVC->IsPerspective())
{
UpdateCameraToInstancesDistance(LevelVC->GetViewLocation());
bLevelEditorInstancesUpdated = true;
break;
}
}
}
}
#endif // WITH_EDITOR
}
}
}
else
{
for (const TWeakObjectPtr<const AActor> ViewCenter : ViewCenters)
{
if (ViewCenter.IsValid())
{
UpdatePawnToInstancesDistances(nullptr, ViewCenter);
}
}
}
}
if (NumGeneratedInstancesLimitLODs > 0)
{
int32 NumGeneratedInstancesLimitLOD1 = GetNumGeneratedInstancesLimitLOD1();
int32 NumGeneratedInstancesLimitLOD2 = GetNumGeneratedInstancesLimitLOD2();
TArray<UCustomizableObjectInstance*> SortedInstances;
for (TObjectIterator<UCustomizableObjectInstance> CustomizableObjectInstance; CustomizableObjectInstance; ++CustomizableObjectInstance)
{
if (IsValid(*CustomizableObjectInstance) &&
CustomizableObjectInstance->GetPrivate() &&
CustomizableObjectInstance->GetIsBeingUsedByComponentInPlay())
{
if (const UCustomizableObject* CustomizableObject = CustomizableObjectInstance->GetCustomizableObject();
CustomizableObject &&
!CustomizableObject->GetPrivate()->IsLocked())
{
UCustomizableObjectInstance* Ptr = *CustomizableObjectInstance;
Ptr->SetIsDiscardedBecauseOfTooManyInstances(false);
SortedInstances.Add(Ptr);
}
}
}
for (int32 i = 0; i < GetNumberOfPriorityUpdateInstances() && i < SortedInstances.Num(); ++i)
{
SortedInstances[i]->SetIsPlayerOrNearIt(true);
}
TMap<FName, uint8> MinLODs;
TMap<FName, uint8> RequestedLODs;
for (int32 i = NumGeneratedInstancesLimitLODs + NumGeneratedInstancesLimitLOD1 + NumGeneratedInstancesLimitLOD2; i < SortedInstances.Num(); ++i)
{
SortedInstances[i]->SetIsDiscardedBecauseOfTooManyInstances(true);
}
if (SortedInstances.Num() > NumGeneratedInstancesLimitLODs)
{
SortedInstances.Sort([](UCustomizableObjectInstance& A, UCustomizableObjectInstance& B)
{
return A.GetMinSquareDistToPlayer() < B.GetMinSquareDistToPlayer();
});
bool bAlreadyReachedFixedLOD = false;
const float DistanceForFixedLOD = float(CVarDistanceForFixedLOD2.GetValueOnGameThread());
const float DistanceForFixedLODSquared = FMath::Square(DistanceForFixedLOD);
for (int32 i = 0; i < NumGeneratedInstancesLimitLODs && i < SortedInstances.Num(); ++i)
{
const UCustomizableObject* CO = SortedInstances[i]->GetCustomizableObject();
if (!bAlreadyReachedFixedLOD && SortedInstances[i]->GetMinSquareDistToPlayer() < DistanceForFixedLODSquared)
{
for (int32 ComponentIndex = 0; ComponentIndex < CO->GetComponentCount(); ++ComponentIndex)
{
MinLODs.Add(CO->GetComponentName(ComponentIndex), 0);
RequestedLODs.Add(CO->GetComponentName(ComponentIndex), 0);
}
SortedInstances[i]->SetRequestedLODs(MinLODs, RequestedLODs, InOutRequestedUpdates);
}
else
{
for (int32 ComponentIndex = 0; ComponentIndex < CO->GetComponentCount(); ++ComponentIndex)
{
MinLODs.Add(CO->GetComponentName(ComponentIndex), UINT8_MAX);
RequestedLODs.Add(CO->GetComponentName(ComponentIndex), 0);
}
SortedInstances[i]->SetRequestedLODs(MinLODs, RequestedLODs, InOutRequestedUpdates);
bAlreadyReachedFixedLOD = true;
}
}
for (int32 i = NumGeneratedInstancesLimitLODs; i < NumGeneratedInstancesLimitLODs + NumGeneratedInstancesLimitLOD1 && i < SortedInstances.Num(); ++i)
{
const UCustomizableObject* CO = SortedInstances[i]->GetCustomizableObject();
if (!bAlreadyReachedFixedLOD && SortedInstances[i]->GetMinSquareDistToPlayer() < DistanceForFixedLODSquared)
{
for (int32 ComponentIndex = 0; ComponentIndex < CO->GetComponentCount(); ++ComponentIndex)
{
MinLODs.Add(CO->GetComponentName(ComponentIndex), 1);
RequestedLODs.Add(CO->GetComponentName(ComponentIndex), 0);
}
SortedInstances[i]->SetRequestedLODs(MinLODs, RequestedLODs, InOutRequestedUpdates);
}
else
{
for (int32 ComponentIndex = 0; ComponentIndex < CO->GetComponentCount(); ++ComponentIndex)
{
MinLODs.Add(CO->GetComponentName(ComponentIndex), UINT8_MAX);
RequestedLODs.Add(CO->GetComponentName(ComponentIndex), 0);
}
SortedInstances[i]->SetRequestedLODs(MinLODs, RequestedLODs, InOutRequestedUpdates);
bAlreadyReachedFixedLOD = true;
}
}
for (int32 i = NumGeneratedInstancesLimitLODs + NumGeneratedInstancesLimitLOD1; i < NumGeneratedInstancesLimitLODs + NumGeneratedInstancesLimitLOD1 + NumGeneratedInstancesLimitLOD2 && i < SortedInstances.Num(); ++i)
{
const UCustomizableObject* CO = SortedInstances[i]->GetCustomizableObject();
for (int32 ComponentIndex = 0; ComponentIndex < CO->GetComponentCount(); ++ComponentIndex)
{
MinLODs.Add(CO->GetComponentName(ComponentIndex), 2);
RequestedLODs.Add(CO->GetComponentName(ComponentIndex), 0);
}
SortedInstances[i]->SetRequestedLODs(MinLODs, RequestedLODs, InOutRequestedUpdates);
}
for (int32 i = NumGeneratedInstancesLimitLODs + NumGeneratedInstancesLimitLOD1 + NumGeneratedInstancesLimitLOD2; i < SortedInstances.Num(); ++i)
{
SortedInstances[i]->SetIsDiscardedBecauseOfTooManyInstances(true);
}
}
else
{
// No limit surpassed, set all instances to have all LODs, there will be an UpdateSkeletalMesh only if there's a change in LOD state
for (int32 i = 0; i < SortedInstances.Num(); ++i)
{
const UCustomizableObject* CO = SortedInstances[i]->GetCustomizableObject();
for (int32 ComponentIndex = 0; ComponentIndex < CO->GetComponentCount(); ++ComponentIndex)
{
MinLODs.Add(CO->GetComponentName(ComponentIndex), 2);
RequestedLODs.Add(CO->GetComponentName(ComponentIndex), 0);
}
SortedInstances[i]->SetRequestedLODs(MinLODs, RequestedLODs, InOutRequestedUpdates);
}
}
}
else if (IsOnlyGenerateRequestedLODLevelsEnabled())
{
struct FLODTracker
{
TMap<FName, uint8> MinLOD;
bool bInitialized = false;
TMap<FName, uint8> RequestedLODPerComponent;
};
TMap<TObjectPtr<UCustomizableObjectInstance>, FLODTracker> InstancesMinLOD;
InstancesMinLOD.Reserve(100);
for (TObjectIterator<UCustomizableObjectInstanceUsage> CustomizableObjectInstanceUsage; CustomizableObjectInstanceUsage; ++CustomizableObjectInstanceUsage)
{
#if WITH_EDITOR
if (IsValid(*CustomizableObjectInstanceUsage) && CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer))
{
continue;
}
#endif
if (IsValid(*CustomizableObjectInstanceUsage) && !CustomizableObjectInstanceUsage->IsTemplate())
{
UCustomizableObjectInstance* COI = CustomizableObjectInstanceUsage->GetCustomizableObjectInstance();
if (!COI || !COI->GetCustomizableObject())
{
continue;
}
USkeletalMeshComponent* Parent = Cast<USkeletalMeshComponent>(CustomizableObjectInstanceUsage->GetAttachParent());
#if WITH_EDITOR
EWorldType::Type WorldType = EWorldType::Type::None;
UWorld* World = Parent ? Parent->GetWorld() : nullptr;
if (World)
{
WorldType = World->WorldType;
}
// Blueprint instances and CO Editors
const USceneComponent* const AttachParentComponent = CustomizableObjectInstanceUsage->GetAttachParent();
bool bAttachParentActor = AttachParentComponent ? AttachParentComponent->GetOwner()!=nullptr : false;
if (WorldType == EWorldType::EditorPreview || (!World && !bAttachParentActor))
{
continue;
}
// Skip if world type is EditorPreview, GamePreview, etc...
if (WorldType == EWorldType::GamePreview || WorldType == EWorldType::GameRPC || WorldType == EWorldType::Inactive)
{
continue;
}
#endif // WITH_EDITOR
FLODTracker& LODTracker = InstancesMinLOD.FindOrAdd(COI);
if (!LODTracker.bInitialized)
{
const UCustomizableObject* CO = COI->GetCustomizableObject();
for (int32 ComponentIndex = 0; ComponentIndex < CO->GetComponentCount(); ++ComponentIndex)
{
LODTracker.RequestedLODPerComponent.Add(CO->GetComponentName(ComponentIndex), MAX_uint8);
}
LODTracker.bInitialized = true;
}
if (Parent)
{
COI->SetIsBeingUsedByComponentInPlay(true);
const FName& ComponentName = CustomizableObjectInstanceUsage->GetComponentName();
#if WITH_EDITOR
// If the instance is generated but the component doesn't have a mesh, set it.
// Can happen when duplicating instances in the editor.
const USkeletalMesh* SkeletalMesh = COI->GetComponentMeshSkeletalMesh(ComponentName);
if (SkeletalMesh && !Parent->GetSkeletalMeshAsset())
{
// As the instance is already generated, this will be very fast and just set the mesh and call the delegates
COI->UpdateSkeletalMeshAsync();
}
#endif
// If it's the local player set max priority
const USceneComponent* const AttachParentParentComponent = Parent->GetAttachParent();
AActor* ParentParentActor = AttachParentParentComponent ? AttachParentParentComponent->GetOwner() : nullptr;
const APawn* Pawn = Cast<APawn>(ParentParentActor);
if (Pawn && Pawn->IsPlayerControlled())
{
COI->SetMinSquareDistToPlayer(-1.f);
}
// Use the component minLOD to set the minimum LOD to generate
uint8& MinLOD = LODTracker.MinLOD.FindOrAdd(ComponentName, UINT8_MAX);
MinLOD = FMath::Min(MinLOD, static_cast<uint8>(Parent->bOverrideMinLod ? Parent->MinLodModel : 0));
// If the parent component have a SkeletalMesh use the RequestedLODLevel of the component as reference to know which LODs mutable should generate.
if (UE_MUTABLE_GETSKELETALMESHASSET(Parent) && LODTracker.RequestedLODPerComponent.Contains(ComponentName))
{
uint8& RequestedLOD = LODTracker.RequestedLODPerComponent[ComponentName];
RequestedLOD = FMath::Min((int32)RequestedLOD, Parent->GetPredictedLODLevel());
}
}
}
}
for (TPair<TObjectPtr<UCustomizableObjectInstance>, FLODTracker>& It : InstancesMinLOD)
{
if (IsValidChecked(It.Key) && It.Key->GetPrivate())
{
const UCustomizableObject* CustomizableObject = It.Key->GetCustomizableObject();
if (!CustomizableObject || CustomizableObject->GetPrivate()->IsLocked())
{
continue;
}
if (IsOnlyUpdateCloseCustomizableObjectsEnabled() && (It.Key->GetMinSquareDistToPlayer() > FMath::Square(CloseCustomizableObjectsDist) || !It.Key->GetPrivate()->NearestToActor.IsValid()))
{
continue;
}
It.Key->SetRequestedLODs(It.Value.MinLOD, It.Value.RequestedLODPerComponent, InOutRequestedUpdates);
}
}
}
}
int32 UCustomizableInstanceLODManagement::GetNumGeneratedInstancesLimitFullLODs() const
{
return CVarNumGeneratedInstancesLimit.GetValueOnGameThread();
}
int32 UCustomizableInstanceLODManagement::GetNumGeneratedInstancesLimitLOD1() const
{
return CVarNumGeneratedInstancesLimitLOD1.GetValueOnGameThread();
}
int32 UCustomizableInstanceLODManagement::GetNumGeneratedInstancesLimitLOD2() const
{
return CVarNumGeneratedInstancesLimitLOD2.GetValueOnGameThread();
}
void UCustomizableInstanceLODManagement::SetNumberOfPriorityUpdateInstances(int32 InNumPriorityUpdateInstances)
{
NumPriorityUpdateInstances = InNumPriorityUpdateInstances;
}
int32 UCustomizableInstanceLODManagement::GetNumberOfPriorityUpdateInstances() const
{
return NumPriorityUpdateInstances;
}
void UCustomizableInstanceLODManagement::SetCustomizableObjectsUpdateDistance(float Distance)
{
CloseCustomizableObjectsDist = Distance;
}
float UCustomizableInstanceLODManagement::GetOnlyUpdateCloseCustomizableObjectsDist() const
{
return CloseCustomizableObjectsDist;
}
bool UCustomizableInstanceLODManagement::IsOnlyUpdateCloseCustomizableObjectsEnabled() const
{
return CVarOnlyUpdateCloseCustomizableObjects.GetValueOnGameThread();
}