615 lines
23 KiB
C++
615 lines
23 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "StatsPages/PrimitiveStatsPage.h"
|
|
#include "Engine/Level.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Serialization/ArchiveCountMem.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Engine/MapBuildDataRegistry.h"
|
|
#include "Model.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Components/ModelComponent.h"
|
|
#include "Engine/Selection.h"
|
|
#include "Editor.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "SceneInterface.h"
|
|
#include "StaticMeshComponentLODInfo.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "LandscapeProxy.h"
|
|
#include "LightMap.h"
|
|
#include "LandscapeComponent.h"
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "Rendering/SkeletalMeshRenderData.h"
|
|
#include "Components/InstancedStaticMeshComponent.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "Editor.StatsViewer.PrimitiveStats"
|
|
|
|
FPrimitiveStatsPage& FPrimitiveStatsPage::Get()
|
|
{
|
|
static FPrimitiveStatsPage* Instance = NULL;
|
|
if( Instance == NULL )
|
|
{
|
|
Instance = new FPrimitiveStatsPage;
|
|
}
|
|
return *Instance;
|
|
}
|
|
|
|
/** Helper class to generate statistics */
|
|
struct PrimitiveStatsGenerator
|
|
{
|
|
/** Map a list of the resources with the data about them */
|
|
TMap<UObject*, UPrimitiveStats*> ResourceToStatsMap;
|
|
|
|
/** Check if the object is in a visible level */
|
|
bool IsInVisibleLevel( UObject* InObject, UWorld* InWorld ) const
|
|
{
|
|
check( InObject );
|
|
check( InWorld );
|
|
|
|
UObject* ObjectTopMostPackage = InObject->GetOutermostObject()->GetPackage();
|
|
|
|
for( int32 LevelIndex=0; LevelIndex<InWorld->GetNumLevels(); LevelIndex++ )
|
|
{
|
|
ULevel* Level = InWorld->GetLevel(LevelIndex);
|
|
if( Level && Level->GetPackage() == ObjectTopMostPackage )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Add a new statistic to the internal map (or update an existing one) from the supplied component */
|
|
UPrimitiveStats* Add(UPrimitiveComponent* InPrimitiveComponent, EPrimitiveObjectSets InObjectSet, bool bAllowTransientWorld)
|
|
{
|
|
// Transient objects are not part of level, but if part of transient package, we can allow depending on flag bAllowTransientWorld.
|
|
if( (!bAllowTransientWorld && InPrimitiveComponent->GetOutermost() == GetTransientPackage()) || InPrimitiveComponent->HasAnyFlags( RF_Transient ) )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Owned by a default object? Not part of a level either.
|
|
if(InPrimitiveComponent->GetOuter() && InPrimitiveComponent->GetOuter()->IsTemplate())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(InPrimitiveComponent);
|
|
UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(InPrimitiveComponent);
|
|
UModelComponent* ModelComponent = Cast<UModelComponent>(InPrimitiveComponent);
|
|
USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(InPrimitiveComponent);
|
|
ULandscapeComponent* LandscapeComponent = Cast<ULandscapeComponent>(InPrimitiveComponent);
|
|
UObject* Resource = NULL;
|
|
AActor* ActorOuter = Cast<AActor>(InPrimitiveComponent->GetOuter());
|
|
|
|
int32 VertexColorMem = 0;
|
|
int32 InstVertexColorMem = 0;
|
|
// Calculate number of direct and other lights relevant to this component.
|
|
int32 LightsLMCount = 0;
|
|
int32 LightsOtherCount = 0;
|
|
bool bUsesOnlyUnlitMaterials = InPrimitiveComponent->UsesOnlyUnlitMaterials();
|
|
int32 NumHWInstances = InstancedStaticMeshComponent ? InstancedStaticMeshComponent->GetNumRenderInstances() : 1;
|
|
|
|
// The static mesh is a static mesh component's resource.
|
|
if( StaticMeshComponent )
|
|
{
|
|
UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
|
|
Resource = Mesh;
|
|
|
|
// Calculate vertex color memory on the actual mesh.
|
|
if( Mesh && Mesh->GetRenderData())
|
|
{
|
|
// Accumulate memory for each LOD
|
|
for( int32 LODIndex = 0; LODIndex < Mesh->GetRenderData()->LODResources.Num(); ++LODIndex )
|
|
{
|
|
VertexColorMem += Mesh->GetRenderData()->LODResources[LODIndex].VertexBuffers.ColorVertexBuffer.GetAllocatedSize();
|
|
}
|
|
}
|
|
|
|
// Calculate instanced vertex color memory used on the component.
|
|
for( int32 LODIndex = 0; LODIndex < StaticMeshComponent->LODData.Num(); ++LODIndex )
|
|
{
|
|
// Accumulate memory for each LOD
|
|
const FStaticMeshComponentLODInfo& LODInfo = StaticMeshComponent->LODData[ LODIndex ];
|
|
if( LODInfo.OverrideVertexColors )
|
|
{
|
|
InstVertexColorMem += LODInfo.OverrideVertexColors->GetAllocatedSize();
|
|
}
|
|
}
|
|
// Calculate the number of lightmap and shadow map lights
|
|
if( !bUsesOnlyUnlitMaterials )
|
|
{
|
|
if( StaticMeshComponent->LODData.Num() > 0 )
|
|
{
|
|
FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[0];
|
|
const FMeshMapBuildData* MeshMapBuildData = StaticMeshComponent->GetMeshMapBuildData(ComponentLODInfo);
|
|
if( MeshMapBuildData && MeshMapBuildData->LightMap )
|
|
{
|
|
LightsLMCount = MeshMapBuildData->LightMap->LightGuids.Num();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// A model component is its own resource.
|
|
else if( ModelComponent )
|
|
{
|
|
// Make sure model component is referenced by level.
|
|
ULevel* Level = CastChecked<ULevel>(ModelComponent->GetOuter());
|
|
if( Level->ModelComponents.Find( ModelComponent ) != INDEX_NONE )
|
|
{
|
|
Resource = ModelComponent->GetModel();
|
|
|
|
// Calculate the number of lightmap and shadow map lights
|
|
if( !bUsesOnlyUnlitMaterials )
|
|
{
|
|
const TIndirectArray<FModelElement> Elements = ModelComponent->GetElements();
|
|
if( Elements.Num() > 0 )
|
|
{
|
|
const FMeshMapBuildData* MeshMapBuildData = Elements[0].GetMeshMapBuildData();
|
|
if( MeshMapBuildData && MeshMapBuildData->LightMap )
|
|
{
|
|
LightsLMCount = MeshMapBuildData->LightMap->LightGuids.Num();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// The skeletal mesh of a skeletal mesh component is its resource.
|
|
else if( SkeletalMeshComponent )
|
|
{
|
|
USkeletalMesh* Mesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
|
|
Resource = Mesh;
|
|
// Calculate vertex color usage for skeletal meshes
|
|
if( Mesh )
|
|
{
|
|
FSkeletalMeshRenderData* SkelMeshRenderData = Mesh->GetResourceForRendering();
|
|
for( int32 LODIndex = 0; LODIndex < SkelMeshRenderData->LODRenderData.Num(); ++LODIndex )
|
|
{
|
|
const FSkeletalMeshLODRenderData& LODData = SkelMeshRenderData->LODRenderData[ LODIndex ];
|
|
VertexColorMem += LODData.StaticVertexBuffers.ColorVertexBuffer.GetAllocatedSize();
|
|
}
|
|
}
|
|
}
|
|
// The landscape of a landscape component is its resource.
|
|
else if (LandscapeComponent)
|
|
{
|
|
Resource = LandscapeComponent->GetLandscapeProxy();
|
|
const FMeshMapBuildData* MeshMapBuildData = LandscapeComponent->GetMeshMapBuildData();
|
|
|
|
if (MeshMapBuildData && MeshMapBuildData->LightMap)
|
|
{
|
|
LightsLMCount = MeshMapBuildData->LightMap->LightGuids.Num();
|
|
}
|
|
}
|
|
|
|
UWorld* World = InPrimitiveComponent->GetWorld();
|
|
// check(World); // @todo: re-instate this check once the GWorld migration has completed
|
|
/// If we should skip the actor. Skip if the actor has no outer or if we are only showing selected actors and the actor isn't selected
|
|
const bool bShouldSkip = ActorOuter == NULL || (ActorOuter != NULL && InObjectSet == PrimitiveObjectSets_SelectedObjects && ActorOuter->IsSelected() == false );
|
|
// Dont' care about components without a resource.
|
|
if( Resource
|
|
&& World != NULL
|
|
// Require actor association for selection and to disregard mesh emitter components. The exception being model components.
|
|
&& (!bShouldSkip || (ModelComponent && InObjectSet != PrimitiveObjectSets_SelectedObjects ) )
|
|
// Only list primitives in visible levels
|
|
&& IsInVisibleLevel( InPrimitiveComponent, World )
|
|
// Don't list pending kill components.
|
|
&& IsValid(InPrimitiveComponent) )
|
|
{
|
|
// Retrieve relevant lights.
|
|
TArray<const ULightComponent*> RelevantLights;
|
|
World->Scene->GetRelevantLights( InPrimitiveComponent, &RelevantLights );
|
|
|
|
// Only look for relevant lights if we aren't unlit.
|
|
if( !bUsesOnlyUnlitMaterials )
|
|
{
|
|
// Lightmap and shadow map lights are calculated above, per component type, infer the "other" light count here
|
|
LightsOtherCount = RelevantLights.Num() >= LightsLMCount ? RelevantLights.Num() - LightsLMCount : 0;
|
|
}
|
|
|
|
// Figure out memory used by light and shadow maps and light/ shadow map resolution.
|
|
int32 LightMapWidth = 0;
|
|
int32 LightMapHeight = 0;
|
|
InPrimitiveComponent->GetLightMapResolution( LightMapWidth, LightMapHeight );
|
|
int32 LMSMResolution = FMath::TruncToInt32(FMath::Sqrt( static_cast<float>(LightMapHeight * LightMapWidth) ));
|
|
int32 LightMapData = 0;
|
|
int32 LegacyShadowMapData = 0;
|
|
InPrimitiveComponent->GetLightAndShadowMapMemoryUsage( LightMapData, LegacyShadowMapData );
|
|
|
|
// Check whether we already have an entry for the associated static mesh.
|
|
UPrimitiveStats** StatsEntryPtr = ResourceToStatsMap.Find( Resource );
|
|
if( StatsEntryPtr )
|
|
{
|
|
check(*StatsEntryPtr);
|
|
UPrimitiveStats* StatsEntry = *StatsEntryPtr;
|
|
|
|
// We do. Update existing entry.
|
|
StatsEntry->Count++;
|
|
StatsEntry->HWInstances += NumHWInstances;
|
|
StatsEntry->Actors.AddUnique(ActorOuter);
|
|
StatsEntry->RadiusMin = FMath::Min( StatsEntry->RadiusMin, InPrimitiveComponent->Bounds.SphereRadius );
|
|
StatsEntry->RadiusMax = FMath::Max( StatsEntry->RadiusMax, InPrimitiveComponent->Bounds.SphereRadius );
|
|
StatsEntry->RadiusAvg += InPrimitiveComponent->Bounds.SphereRadius;
|
|
StatsEntry->LightsLM += LightsLMCount;
|
|
StatsEntry->LightsOther += LightsOtherCount;
|
|
StatsEntry->LightMapData += (float)LightMapData / 1024.0f;
|
|
StatsEntry->LMSMResolution += LMSMResolution;
|
|
StatsEntry->UpdateNames();
|
|
|
|
if ( !ModelComponent && !LandscapeComponent )
|
|
{
|
|
// Count instanced sections
|
|
StatsEntry->InstSections += StatsEntry->Sections * NumHWInstances;
|
|
StatsEntry->InstTriangles += StatsEntry->Triangles * NumHWInstances;
|
|
}
|
|
|
|
// ... in the case of a model component (aka BSP).
|
|
if( ModelComponent )
|
|
{
|
|
// If Count represents the Model itself, we do NOT want to increment it now.
|
|
StatsEntry->Count--;
|
|
|
|
for (const auto& Element : ModelComponent->GetElements())
|
|
{
|
|
StatsEntry->Triangles += Element.NumTriangles;
|
|
StatsEntry->Sections++;
|
|
}
|
|
|
|
StatsEntry->InstSections = StatsEntry->Sections;
|
|
StatsEntry->InstTriangles = StatsEntry->Triangles;
|
|
}
|
|
else if( StaticMeshComponent )
|
|
{
|
|
// This stat is used by multiple components so accumulate instanced vertex color memory.
|
|
StatsEntry->InstVertexColorMem += (float)InstVertexColorMem / 1024.0f;
|
|
}
|
|
else if (LandscapeComponent)
|
|
{
|
|
// If Count represents the Landscape itself, we do NOT want to increment it now.
|
|
StatsEntry->Count--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We don't. Create new base entry.
|
|
UPrimitiveStats* NewStatsEntry = NewObject<UPrimitiveStats>();
|
|
NewStatsEntry->AddToRoot();
|
|
NewStatsEntry->Object = Resource;
|
|
NewStatsEntry->Actors.AddUnique(ActorOuter);
|
|
NewStatsEntry->Count = 1;
|
|
NewStatsEntry->HWInstances = NumHWInstances;
|
|
NewStatsEntry->Triangles = 0;
|
|
NewStatsEntry->InstTriangles = 0;
|
|
NewStatsEntry->ResourceSize = (float)(FArchiveCountMem(Resource).GetNum() + Resource->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal)) / 1024.0f;
|
|
NewStatsEntry->Sections = 0;
|
|
NewStatsEntry->InstSections = 0;
|
|
NewStatsEntry->RadiusMin = InPrimitiveComponent->Bounds.SphereRadius;
|
|
NewStatsEntry->RadiusAvg = InPrimitiveComponent->Bounds.SphereRadius;
|
|
NewStatsEntry->RadiusMax = InPrimitiveComponent->Bounds.SphereRadius;
|
|
NewStatsEntry->LightsLM = LightsLMCount;
|
|
NewStatsEntry->LightsOther = (float)LightsOtherCount;
|
|
NewStatsEntry->LightMapData = (float)LightMapData / 1024.0f;
|
|
NewStatsEntry->LMSMResolution = (float)LMSMResolution;
|
|
NewStatsEntry->VertexColorMem = (float)VertexColorMem / 1024.0f;
|
|
NewStatsEntry->InstVertexColorMem = (float)InstVertexColorMem / 1024.0f;
|
|
NewStatsEntry->UpdateNames();
|
|
|
|
// Fix up triangle and section count...
|
|
|
|
// ... in the case of a static mesh component.
|
|
if( StaticMeshComponent )
|
|
{
|
|
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
if( StaticMesh && StaticMesh->GetRenderData())
|
|
{
|
|
for( int32 SectionIndex=0; SectionIndex<StaticMesh->GetRenderData()->LODResources[0].Sections.Num(); SectionIndex++ )
|
|
{
|
|
const FStaticMeshSection& StaticMeshSection = StaticMesh->GetRenderData()->LODResources[0].Sections[SectionIndex];
|
|
NewStatsEntry->Triangles += StaticMeshSection.NumTriangles;
|
|
NewStatsEntry->Sections++;
|
|
}
|
|
}
|
|
}
|
|
// ... in the case of a model component (aka BSP).
|
|
else if( ModelComponent )
|
|
{
|
|
TIndirectArray<FModelElement> Elements = ModelComponent->GetElements();
|
|
for( int32 ElementIndex=0; ElementIndex<Elements.Num(); ElementIndex++ )
|
|
{
|
|
const FModelElement& Element = Elements[ElementIndex];
|
|
NewStatsEntry->Triangles += Element.NumTriangles;
|
|
NewStatsEntry->Sections++;
|
|
}
|
|
|
|
}
|
|
// ... in the case of skeletal mesh component.
|
|
else if( SkeletalMeshComponent )
|
|
{
|
|
USkeletalMesh* SkeletalMesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
|
|
if( SkeletalMesh )
|
|
{
|
|
FSkeletalMeshRenderData* SkelMeshRenderData = SkeletalMesh->GetResourceForRendering();
|
|
if (SkelMeshRenderData->LODRenderData.Num())
|
|
{
|
|
const FSkeletalMeshLODRenderData& BaseLOD = SkelMeshRenderData->LODRenderData[0];
|
|
for( int32 SectionIndex=0; SectionIndex<BaseLOD.RenderSections.Num(); SectionIndex++ )
|
|
{
|
|
const FSkelMeshRenderSection& Section = BaseLOD.RenderSections[SectionIndex];
|
|
NewStatsEntry->Triangles += Section.NumTriangles;
|
|
NewStatsEntry->Sections++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (LandscapeComponent)
|
|
{
|
|
TSet<UTexture2D*> UniqueTextures;
|
|
for (auto ItComponents = LandscapeComponent->GetLandscapeProxy()->LandscapeComponents.CreateConstIterator(); ItComponents; ++ItComponents)
|
|
{
|
|
const ULandscapeComponent* CurrentComponent = *ItComponents;
|
|
|
|
// count triangles and sections in the landscape
|
|
NewStatsEntry->Triangles += FMath::Square(CurrentComponent->ComponentSizeQuads) * 2;
|
|
NewStatsEntry->Sections += FMath::Square(CurrentComponent->NumSubsections);
|
|
|
|
// count resource usage of landscape
|
|
bool bNotUnique = false;
|
|
UniqueTextures.Add(CurrentComponent->GetHeightmap(), &bNotUnique);
|
|
if (!bNotUnique)
|
|
{
|
|
const SIZE_T HeightmapResourceSize = CurrentComponent->GetHeightmap()->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal);
|
|
NewStatsEntry->ResourceSize += (float)HeightmapResourceSize / 1024.0f;
|
|
}
|
|
if (CurrentComponent->XYOffsetmapTexture)
|
|
{
|
|
UniqueTextures.Add(CurrentComponent->XYOffsetmapTexture, &bNotUnique);
|
|
if (!bNotUnique)
|
|
{
|
|
const SIZE_T OffsetmapResourceSize = CurrentComponent->XYOffsetmapTexture->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal);
|
|
NewStatsEntry->ResourceSize += (float)OffsetmapResourceSize / 1024.f;
|
|
}
|
|
}
|
|
|
|
const TArray<UTexture2D*>& ComponentWeightmapTextures = CurrentComponent->GetWeightmapTextures();
|
|
|
|
for (UTexture2D* Weightmap : ComponentWeightmapTextures)
|
|
{
|
|
UniqueTextures.Add(Weightmap, &bNotUnique);
|
|
if (!bNotUnique)
|
|
{
|
|
const SIZE_T WeightmapResourceSize = Weightmap->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal);
|
|
NewStatsEntry->ResourceSize += (float)WeightmapResourceSize / 1024.f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NewStatsEntry->InstTriangles = NewStatsEntry->Triangles * NumHWInstances;
|
|
NewStatsEntry->InstSections = NewStatsEntry->Sections * NumHWInstances;
|
|
|
|
// Add to map.
|
|
ResourceToStatsMap.Add( Resource, NewStatsEntry );
|
|
|
|
return NewStatsEntry;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** Called once all stats are gathered into the map */
|
|
void Generate()
|
|
{
|
|
// consolidate averages etc.
|
|
for( auto It = ResourceToStatsMap.CreateIterator(); It; ++It )
|
|
{
|
|
It.Value()->InstTriangles = It.Value()->Count * It.Value()->Triangles;
|
|
It.Value()->LightsTotal = ( (float)It.Value()->LightsLM + It.Value()->LightsOther ) / (float)It.Value()->Count;
|
|
It.Value()->ObjLightCost = It.Value()->LightsOther * It.Value()->Sections;
|
|
It.Value()->LightsOther = It.Value()->LightsOther / It.Value()->Count;
|
|
It.Value()->RadiusAvg /= It.Value()->Count;
|
|
It.Value()->LMSMResolution /= It.Value()->Count;
|
|
}
|
|
}
|
|
};
|
|
|
|
void FPrimitiveStatsPage::Generate(TArray< TWeakObjectPtr<UObject> >& OutObjects) const
|
|
{
|
|
PrimitiveStatsGenerator Generator;
|
|
Generator.Generate();
|
|
|
|
UWorld* World = GetWorld();
|
|
bool bAllowTransientWorld = false;
|
|
|
|
if (StatsWorld.IsValid())
|
|
{
|
|
bAllowTransientWorld = (StatsWorld->GetOutermost() == GetTransientPackage());
|
|
}
|
|
|
|
switch ((EPrimitiveObjectSets)ObjectSetIndex)
|
|
{
|
|
case PrimitiveObjectSets_CurrentLevel:
|
|
{
|
|
for (TObjectIterator<UPrimitiveComponent> It; It; ++It)
|
|
{
|
|
AActor* Owner = (*It)->GetOwner();
|
|
|
|
if (Owner != nullptr && !Owner->HasAnyFlags(RF_ClassDefaultObject) && Owner->IsInLevel(World->GetCurrentLevel()))
|
|
{
|
|
UPrimitiveStats* StatsEntry = Generator.Add(*It, (EPrimitiveObjectSets)ObjectSetIndex, bAllowTransientWorld);
|
|
|
|
if (StatsEntry != nullptr)
|
|
{
|
|
OutObjects.Add(StatsEntry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PrimitiveObjectSets_AllObjects:
|
|
{
|
|
if (World)
|
|
{
|
|
TArray<ULevel*> Levels;
|
|
|
|
// Add main level.
|
|
Levels.AddUnique(World->PersistentLevel);
|
|
|
|
// Add secondary levels.
|
|
for (ULevelStreaming* StreamingLevel : World->GetStreamingLevels())
|
|
{
|
|
if (StreamingLevel)
|
|
{
|
|
if (ULevel* Level = StreamingLevel->GetLoadedLevel())
|
|
{
|
|
Levels.AddUnique(Level);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TObjectIterator<UPrimitiveComponent> It; It; ++It)
|
|
{
|
|
AActor* Owner = (*It)->GetOwner();
|
|
|
|
if (Owner != nullptr && !Owner->HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
ULevel* CheckLevel = Owner->GetLevel();
|
|
|
|
if (CheckLevel != nullptr && (Levels.Contains(CheckLevel)))
|
|
{
|
|
UPrimitiveStats* StatsEntry = Generator.Add(*It, (EPrimitiveObjectSets)ObjectSetIndex, bAllowTransientWorld);
|
|
|
|
if (StatsEntry != nullptr)
|
|
{
|
|
OutObjects.Add(StatsEntry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PrimitiveObjectSets_SelectedObjects:
|
|
{
|
|
TArray<UObject*> SelectedActors;
|
|
GEditor->GetSelectedActors()->GetSelectedObjects(AActor::StaticClass(), SelectedActors);
|
|
|
|
for (TObjectIterator<UPrimitiveComponent> It; It; ++It)
|
|
{
|
|
AActor* Owner = (*It)->GetOwner();
|
|
if (Owner != nullptr && !Owner->HasAnyFlags(RF_ClassDefaultObject) && SelectedActors.Contains(Owner))
|
|
{
|
|
UPrimitiveStats* StatsEntry = Generator.Add(*It, (EPrimitiveObjectSets)ObjectSetIndex, bAllowTransientWorld);
|
|
if (StatsEntry != nullptr)
|
|
{
|
|
OutObjects.Add(StatsEntry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FPrimitiveStatsPage::GenerateTotals( const TArray< TWeakObjectPtr<UObject> >& InObjects, TMap<FString, FText>& OutTotals ) const
|
|
{
|
|
if(InObjects.Num())
|
|
{
|
|
UPrimitiveStats* TotalEntry = NewObject<UPrimitiveStats>();
|
|
|
|
TotalEntry->RadiusMin = TNumericLimits<double>::Max();
|
|
TotalEntry->RadiusMax = 0.0f;
|
|
|
|
// build total entry
|
|
for( auto It = InObjects.CreateConstIterator(); It; ++It )
|
|
{
|
|
UPrimitiveStats* StatsEntry = Cast<UPrimitiveStats>( It->Get() );
|
|
|
|
TotalEntry->Count += StatsEntry->Count;
|
|
TotalEntry->Sections += StatsEntry->Sections;
|
|
TotalEntry->HWInstances += StatsEntry->HWInstances;
|
|
TotalEntry->InstSections += StatsEntry->InstSections;
|
|
TotalEntry->Triangles += StatsEntry->Triangles;
|
|
TotalEntry->InstTriangles += StatsEntry->InstTriangles;
|
|
TotalEntry->ResourceSize += StatsEntry->ResourceSize;
|
|
TotalEntry->VertexColorMem += StatsEntry->VertexColorMem;
|
|
TotalEntry->InstVertexColorMem += StatsEntry->InstVertexColorMem;
|
|
TotalEntry->LightsLM += StatsEntry->LightsLM;
|
|
TotalEntry->LightsOther += StatsEntry->LightsOther;
|
|
TotalEntry->LightsTotal += StatsEntry->LightsTotal;
|
|
TotalEntry->ObjLightCost += FMath::TruncToFloat( StatsEntry->ObjLightCost );
|
|
TotalEntry->LightMapData += StatsEntry->LightMapData;
|
|
TotalEntry->LMSMResolution += StatsEntry->LMSMResolution;
|
|
TotalEntry->RadiusMin = FMath::Min(TotalEntry->RadiusMin, StatsEntry->RadiusMin);
|
|
TotalEntry->RadiusMax = FMath::Max(TotalEntry->RadiusMax, StatsEntry->RadiusMax);
|
|
TotalEntry->RadiusAvg += StatsEntry->RadiusAvg;
|
|
}
|
|
|
|
TotalEntry->LMSMResolution /= InObjects.Num();
|
|
TotalEntry->LightsTotal /= InObjects.Num();
|
|
TotalEntry->LightsOther /= InObjects.Num();
|
|
TotalEntry->RadiusAvg /= InObjects.Num();
|
|
|
|
OutTotals.Add( TEXT("Count"), FText::AsNumber( TotalEntry->Count ) );
|
|
OutTotals.Add( TEXT("HWInstances"), FText::AsNumber(TotalEntry->HWInstances) );
|
|
OutTotals.Add( TEXT("InstSections"), FText::AsNumber( TotalEntry->InstSections ) );
|
|
OutTotals.Add( TEXT("Triangles"), FText::AsNumber( TotalEntry->Triangles ) );
|
|
OutTotals.Add( TEXT("InstTriangles"), FText::AsNumber( TotalEntry->InstTriangles ) );
|
|
OutTotals.Add( TEXT("ResourceSize"), FText::AsNumber( TotalEntry->ResourceSize ) );
|
|
OutTotals.Add( TEXT("VertexColorMem"), FText::AsNumber( TotalEntry->VertexColorMem ) );
|
|
OutTotals.Add( TEXT("InstVertexColorMem"), FText::AsNumber( TotalEntry->InstVertexColorMem ) );
|
|
OutTotals.Add( TEXT("LightsLM"), FText::AsNumber( TotalEntry->LightsLM ) );
|
|
OutTotals.Add( TEXT("LightsOther"), FText::AsNumber( TotalEntry->LightsOther ) );
|
|
OutTotals.Add( TEXT("LightsTotal"), FText::AsNumber( TotalEntry->LightsTotal ) );
|
|
OutTotals.Add( TEXT("ObjLightCost"), FText::AsNumber( TotalEntry->ObjLightCost ) );
|
|
OutTotals.Add( TEXT("LightMapData"), FText::AsNumber( TotalEntry->LightMapData ) );
|
|
OutTotals.Add( TEXT("LMSMResolution"), FText::AsNumber( TotalEntry->LMSMResolution ) );
|
|
OutTotals.Add( TEXT("RadiusMin"), FText::AsNumber( TotalEntry->RadiusMin ) );
|
|
OutTotals.Add( TEXT("RadiusMax"), FText::AsNumber( TotalEntry->RadiusMax ) );
|
|
OutTotals.Add( TEXT("RadiusAvg"), FText::AsNumber( TotalEntry->RadiusAvg ) );
|
|
}
|
|
}
|
|
|
|
void FPrimitiveStatsPage::OnEditorSelectionChanged( UObject* NewSelection, TWeakPtr< IStatsViewer > InParentStatsViewer )
|
|
{
|
|
if(InParentStatsViewer.IsValid())
|
|
{
|
|
const int32 ObjSetIndex = InParentStatsViewer.Pin()->GetObjectSetIndex();
|
|
if( ObjSetIndex == PrimitiveObjectSets_SelectedObjects )
|
|
{
|
|
InParentStatsViewer.Pin()->Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveStatsPage::OnEditorNewCurrentLevel( TWeakPtr< IStatsViewer > InParentStatsViewer )
|
|
{
|
|
if(InParentStatsViewer.IsValid())
|
|
{
|
|
const int32 ObjSetIndex = InParentStatsViewer.Pin()->GetObjectSetIndex();
|
|
if( ObjSetIndex == PrimitiveObjectSets_CurrentLevel )
|
|
{
|
|
InParentStatsViewer.Pin()->Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveStatsPage::OnShow( TWeakPtr< IStatsViewer > InParentStatsViewer )
|
|
{
|
|
// register delegates for scene changes we are interested in
|
|
USelection::SelectionChangedEvent.AddRaw(this, &FPrimitiveStatsPage::OnEditorSelectionChanged, InParentStatsViewer);
|
|
FEditorDelegates::NewCurrentLevel.AddRaw(this, &FPrimitiveStatsPage::OnEditorNewCurrentLevel, InParentStatsViewer);
|
|
}
|
|
|
|
void FPrimitiveStatsPage::OnHide()
|
|
{
|
|
// unregister delegates
|
|
USelection::SelectionChangedEvent.RemoveAll(this);
|
|
FEditorDelegates::NewCurrentLevel.RemoveAll(this);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |