// Copyright Epic Games, Inc. All Rights Reserved. #include "GeometryCollectionISMPoolDebugDrawComponent.h" #include "CollisionQueryParams.h" #include "ComponentRecreateRenderStateContext.h" #include "Components/HierarchicalInstancedStaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "Engine/World.h" #include "EngineUtils.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "GeometryCollectionISMPoolActor.h" #include "GeometryCollectionISMPoolComponent.h" #include "Misc/CoreDelegates.h" #if WITH_EDITOR #include "Editor.h" #include "EditorViewportClient.h" #include "LevelEditorViewport.h" #endif #include UE_INLINE_GENERATED_CPP_BY_NAME(GeometryCollectionISMPoolDebugDrawComponent) static TAutoConsoleVariable CVarISMPoolStats( TEXT("p.Chaos.GC.ISMPoolDebugStats"), 0, TEXT("Show stats for the ISM pools"), PRAGMA_DISABLE_DEPRECATION_WARNINGS FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { UGeometryCollectionISMPoolDebugDrawComponent::UpdateAllTickEnabled(); }), PRAGMA_ENABLE_DEPRECATION_WARNINGS ECVF_Default); static TAutoConsoleVariable CVarISMPoolDebugDraw( TEXT("p.Chaos.GC.ISMPoolDebugDraw"), 0, TEXT("Show debug drawing for the ISM pools"), PRAGMA_DISABLE_DEPRECATION_WARNINGS FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { UGeometryCollectionISMPoolDebugDrawComponent::UpdateAllTickEnabled(); FGlobalComponentRecreateRenderStateContext Context; }), PRAGMA_ENABLE_DEPRECATION_WARNINGS ECVF_Default); class FGeometryCollectionISMPoolDebugDrawSceneProxy final : public FDebugRenderSceneProxy { public: SIZE_T GetTypeHash() const override { static size_t UniquePointer; return reinterpret_cast(&UniquePointer); } PRAGMA_DISABLE_DEPRECATION_WARNINGS FGeometryCollectionISMPoolDebugDrawSceneProxy(UGeometryCollectionISMPoolDebugDrawComponent const* InComponent) : FDebugRenderSceneProxy(InComponent) { DrawType = FDebugRenderSceneProxy::SolidAndWireMeshes; } PRAGMA_ENABLE_DEPRECATION_WARNINGS FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { FPrimitiveViewRelevance Result; Result.bDrawRelevance = true; Result.bDynamicRelevance = true; Result.bSeparateTranslucency = Result.bNormalTranslucency = true; return Result; } }; UGeometryCollectionISMPoolDebugDrawComponent::UGeometryCollectionISMPoolDebugDrawComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bIsEditorOnly = false; bSelectable = false; PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bStartWithTickEnabled = false; PrimaryComponentTick.bAllowTickOnDedicatedServer = false; } void UGeometryCollectionISMPoolDebugDrawComponent::UpdateTickEnabled() { const bool bForceShowStats = CVarISMPoolStats.GetValueOnAnyThread() != 0; const bool bForceShowBounds = CVarISMPoolDebugDraw.GetValueOnAnyThread() != 0; PrimaryComponentTick.SetTickFunctionEnable(bShowStats || bForceShowStats || bShowBounds || bForceShowBounds); } void UGeometryCollectionISMPoolDebugDrawComponent::UpdateAllTickEnabled() { PRAGMA_DISABLE_DEPRECATION_WARNINGS for (TObjectIterator It; It; ++It) { It->UpdateTickEnabled(); } PRAGMA_ENABLE_DEPRECATION_WARNINGS } void UGeometryCollectionISMPoolDebugDrawComponent::BeginPlay() { Super::BeginPlay(); #if UE_ENABLE_DEBUG_DRAWING OnScreenMessagesHandle = FCoreDelegates::OnGetOnScreenMessages.AddUObject(this, &UGeometryCollectionISMPoolDebugDrawComponent::GetOnScreenMessages); #endif UpdateTickEnabled(); } void UGeometryCollectionISMPoolDebugDrawComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); #if UE_ENABLE_DEBUG_DRAWING FCoreDelegates::OnGetOnScreenMessages.Remove(OnScreenMessagesHandle); #endif } FBoxSphereBounds UGeometryCollectionISMPoolDebugDrawComponent::CalcBounds(const FTransform& LocalToWorld) const { return SelectedComponent ? SelectedComponent->CalcBounds(LocalToWorld) : FBox(ForceInitToZero); } #if WITH_EDITOR void UGeometryCollectionISMPoolDebugDrawComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); if (PropertyChangedEvent.Property) { if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollectionISMPoolDebugDrawComponent, bShowStats) || PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollectionISMPoolDebugDrawComponent, bShowBounds)) { UpdateTickEnabled(); } } } #endif void UGeometryCollectionISMPoolDebugDrawComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); #if UE_ENABLE_DEBUG_DRAWING UInstancedStaticMeshComponent const* FoundISM = nullptr; const bool bForceShowStats = CVarISMPoolStats.GetValueOnAnyThread() != 0; const bool bForceShowBounds = CVarISMPoolDebugDraw.GetValueOnAnyThread() != 0; if (bShowStats || bForceShowStats || bShowBounds || bForceShowBounds) { #if WITH_EDITOR if (GCurrentLevelEditingViewportClient) { FViewport* Viewport = GCurrentLevelEditingViewportClient->Viewport; HHitProxy* HitResult = Viewport->GetHitProxy(Viewport->GetMouseX(), Viewport->GetMouseY()); if (HActor* HitActor = HitProxyCast(HitResult)) { FoundISM = Cast(HitActor->PrimComponent); } } #endif if (FoundISM == nullptr) { if (UWorld* World = GetWorld()) { APlayerController const* Controller = nullptr; for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator) { APlayerController* PC = Iterator->Get(); if (PC && PC->IsLocalController()) { Controller = PC; } } if (Controller != nullptr) { FVector CamLoc; FRotator CamRot; Controller->GetPlayerViewPoint(CamLoc, CamRot); const FVector CamForward = CamRot.Vector(); const FVector TraceStart = CamLoc; const FVector TraceEnd = TraceStart + CamForward * 20000; FHitResult HitResult(ForceInit); FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(ChaosDebugVisibilityTrace), true, Controller->GetPawn()); const bool bHit = World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_Visibility, TraceParams); if (bHit && HitResult.GetComponent() != nullptr) { if (UInstancedStaticMeshComponent* ISMComponent = Cast(HitResult.GetComponent())) { FoundISM = ISMComponent; } } } } } } if (FoundISM != nullptr && FoundISM->GetOuter() != GetOuter()) { FoundISM = nullptr; } if (SelectedComponent != FoundISM) { bool bChangeSelection = (SelectedComponent == nullptr) || SelectTimer < 0.f; if (bChangeSelection) { #if WITH_EDITOR if (SelectedComponent) { ((UInstancedStaticMeshComponent*)SelectedComponent)->PushHoveredToProxy(false); } #endif SelectedComponent = FoundISM; #if WITH_EDITOR if (SelectedComponent) { ((UInstancedStaticMeshComponent*)SelectedComponent)->PushHoveredToProxy(true); } #endif SelectTimer = 0.3f; MarkRenderStateDirty(); } else { SelectTimer -= DeltaTime; } } #endif // UE_ENABLE_DEBUG_DRAWING } #if UE_ENABLE_DEBUG_DRAWING static void DrawBoxAndChildren(FDebugRenderSceneProxy* DebugProxy, FTransform const& LocalToWorldTransform, TArray const& Nodes, int32 NodeIndex, int32 LevelIndex) { FClusterNode const& Node = Nodes[NodeIndex]; const FBox LocalBounds(FBox(Node.BoundMin, Node.BoundMax)); static TArray Colors = { FColorList::VeryLightGrey, FColorList::CoolCopper, FColorList::GreenYellow, FColorList::CornFlowerBlue, FColorList::DustyRose, FColorList::Red, FColorList::Magenta }; const FColor LevelColor = Colors[FMath::Min(LevelIndex, Colors.Num() - 1)]; DebugProxy->Boxes.Add(FDebugRenderSceneProxy::FDebugBox(LocalBounds, LevelColor, LocalToWorldTransform)); if (Node.FirstChild > 0) { for (int32 ChildIndex = Node.FirstChild; ChildIndex <= Node.LastChild; ChildIndex++) { DrawBoxAndChildren(DebugProxy, LocalToWorldTransform, Nodes, ChildIndex, LevelIndex + 1); } } } PRAGMA_DISABLE_DEPRECATION_WARNINGS FDebugRenderSceneProxy* UGeometryCollectionISMPoolDebugDrawComponent::CreateDebugSceneProxy() PRAGMA_ENABLE_DEPRECATION_WARNINGS { const bool bForceShowBounds = CVarISMPoolDebugDraw.GetValueOnAnyThread() != 0; if (!(SelectedComponent && (bShowBounds || bForceShowBounds))) { return nullptr; } FDebugRenderSceneProxy* DebugProxy = new FGeometryCollectionISMPoolDebugDrawSceneProxy(this); if (const UHierarchicalInstancedStaticMeshComponent* HISMComponent = Cast(SelectedComponent)) { const FTransform LocalToWorldTransform = HISMComponent->GetComponentTransform(); TArray TreeNodes; HISMComponent->GetTree(TreeNodes); DrawBoxAndChildren(DebugProxy, LocalToWorldTransform, TreeNodes, 0, 0); } else if(const UInstancedStaticMeshComponent* ISMComponent = Cast(SelectedComponent)) { const FBox LocalBounds = ISMComponent->CalcBounds(FTransform::Identity).GetBox(); const FTransform LocalToWorldTransform = ISMComponent->GetComponentTransform(); DebugProxy->Boxes.Add(FDebugRenderSceneProxy::FDebugBox(LocalBounds, FColorList::CornFlowerBlue, LocalToWorldTransform)); } return DebugProxy; } void UGeometryCollectionISMPoolDebugDrawComponent::GetOnScreenMessages(TMultiMap& OutMessages) { const bool bForceShowStats = CVarISMPoolStats.GetValueOnAnyThread() != 0; if (!bShowStats && !bShowGlobalStats && !bForceShowStats) { return; } if (bShowGlobalStats || bForceShowStats) { PRAGMA_DISABLE_DEPRECATION_WARNINGS AGeometryCollectionISMPoolActor const* ISMPoolActor = Cast(GetOuter()); UGeometryCollectionISMPoolComponent const* ISMPoolComponent = ISMPoolActor ? ISMPoolActor->GetISMPoolComp() : nullptr; PRAGMA_ENABLE_DEPRECATION_WARNINGS if (ISMPoolComponent) { int32 NumISMs = 0; int32 NumHISMs = 0; int32 NumInstancesTotal = 0; TArray NumInstancesPerISM; PRAGMA_DISABLE_DEPRECATION_WARNINGS NumInstancesPerISM.Reserve(ISMPoolComponent->Pool.ISMs.Num()); PRAGMA_ENABLE_DEPRECATION_WARNINGS for (FGeometryCollectionISM const& ISM : ISMPoolComponent->Pool.ISMs) { if (ISM.ISMComponent != nullptr) { const bool bIsHISM = ISM.ISMComponent->IsA(UHierarchicalInstancedStaticMeshComponent::StaticClass()); NumISMs += bIsHISM ? 0 : 1; NumHISMs += bIsHISM ? 1 : 0; const int32 NumInstances = ISM.ISMComponent->GetNumRenderInstances(); NumInstancesTotal += NumInstances; NumInstancesPerISM.Add(NumInstances); } } if (NumInstancesTotal > 0) { OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Warning, FText::FromString(*GetOwner()->GetPathName())); OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromString(FString::Printf(TEXT("Num ISMS %d"), NumISMs))); OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromString(FString::Printf(TEXT("Num HISMS %d"), NumHISMs))); OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromString(FString::Printf(TEXT("Num Instances %d"), NumInstancesTotal))); OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromString(FString::Printf(TEXT("Average Instances Per ISM %f"), (float)NumInstancesTotal / (float)(NumISMs + NumHISMs)))); NumInstancesPerISM.Sort(); for (int32 Index = 0; Index < 10; ++Index) { const int32 Percentile = (Index + 1) * 10; const int32 PercentileIndex = FMath::Min(NumInstancesPerISM.Num() * (Index + 1) / 10, NumInstancesPerISM.Num() - 1); OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromString(FString::Printf(TEXT("Num Instances at %d percentile: %d"), Percentile, NumInstancesPerISM[PercentileIndex]))); } } } } if ((bShowStats || bForceShowStats) && SelectedComponent) { TCHAR const* ISMType = SelectedComponent->IsA(UHierarchicalInstancedStaticMeshComponent::StaticClass()) ? TEXT("HISM") : TEXT("ISM"); FVector BoundsMin, BoundsMax; SelectedComponent->GetLocalBounds(BoundsMin, BoundsMax); const FVector BoundsSize = BoundsMax - BoundsMin; const int32 NumInstances = SelectedComponent->GetNumRenderInstances(); const int32 NumCustomDataFloats = SelectedComponent->NumCustomDataFloats; const int32 StartCullDistance = SelectedComponent->InstanceStartCullDistance; const int32 EndCullDistance = SelectedComponent->InstanceEndCullDistance; const int32 NumMaterials = SelectedComponent->GetNumMaterials(); UStaticMesh const* StaticMesh = SelectedComponent->GetStaticMesh(); const int32 NumLods = StaticMesh ? StaticMesh->GetNumLODs() : 0; FString ISMDescription = FString::Printf(TEXT("Type=%s Count=%d Bounds=(%f., %f., %f.) CullDistance=(%d - %d) NumLods= %d NumMaterials=%d CustomDataSize=%d"), ISMType, NumInstances, BoundsSize.X, BoundsSize.Z, BoundsSize.Z, StartCullDistance, EndCullDistance, NumLods, NumMaterials, NumCustomDataFloats); for (int32 LodIndex = 0; LodIndex < NumLods; ++LodIndex) { const int32 NumVertices = StaticMesh->GetNumVertices(LodIndex); const int32 NumTriangles = StaticMesh->GetNumTriangles(LodIndex); ISMDescription += FString::Printf(TEXT("\nLod %d: Vertices=%d, Triangles=%d."), LodIndex, NumVertices, NumTriangles); } OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Warning, FText::FromString(*GetOwner()->GetPathName())); OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Warning, FText::FromString(*SelectedComponent->GetName())); OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromString(ISMDescription)); } } #endif