Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/ViewDebug.cpp
2025-05-18 13:04:45 +08:00

339 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ViewDebug.h"
#include "SkeletalRenderPublic.h"
#include "Kismet/GameplayStatics.h"
#if !UE_BUILD_SHIPPING
#include "ScenePrivate.h"
#include "SceneInterface.h"
#include "SceneView.h"
#include "Materials/Material.h"
#include "MaterialShared.h"
#include "ProfilingDebugging/DiagnosticTable.h"
#include "Engine/StaticMesh.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/LocalPlayer.h"
#include "Engine/GameViewportClient.h"
#include "UnrealClient.h"
#include "Materials/MaterialInterface.h"
#include "Components/PrimitiveComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
static bool bDumpPrimitiveDrawCallsNextFrame = false;
static bool bDumpDetailedPrimitivesNextFrame = false;
static FAutoConsoleCommand CVarDumpPrimitives(
TEXT("DumpPrimitiveDrawCalls"),
TEXT("Writes the draw call count of all primitives tracked by the PrimitiveDebugger to a CSV file"),
FConsoleCommandDelegate::CreateStatic([] { bDumpPrimitiveDrawCallsNextFrame = true; }),
ECVF_Default);
static FAutoConsoleCommand CVarDrawPrimitiveDebugData(
TEXT("DumpDetailedPrimitives"),
TEXT("Writes the detailed information of all primitives tracked by the PrimitiveDebugger to a CSV file"),
FConsoleCommandDelegate::CreateStatic([] { bDumpDetailedPrimitivesNextFrame = !bDumpDetailedPrimitivesNextFrame; }),
ECVF_Default);
FViewDebugInfo FViewDebugInfo::Instance;
FViewDebugInfo::FViewDebugInfo()
{
bHasEverUpdated = false;
bIsOutdated = true;
bShouldUpdate = false;
bShouldCaptureSingleFrame = false;
bShouldClearCapturedData = false;
}
int32 FViewDebugInfo::FPrimitiveInfo::ComputeCurrentLODIndex(int32 PlayerIndex, int32 ViewIndex) const
{
if (!IsPrimitiveValid() || !ComponentInterface->GetSceneProxy()) return INDEX_NONE;
if (const USkinnedMeshComponent* SkinnedMesh = ComponentInterface->GetUObject<USkinnedMeshComponent>())
{
if (SkinnedMesh->MeshObject)
{
// Skinned meshes do not implement the GetLOD function for proxies, instead grab it from the mesh object
return SkinnedMesh->MeshObject->GetLOD();
}
}
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(Owner.Get(), PlayerIndex);
if (!IsValid(PlayerController))
{
return INDEX_NONE;
}
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->Player);
if (IsValid(LocalPlayer) && IsValid(LocalPlayer->ViewportClient))
{
// see: AHUD::GetCoordinateOffset() and UGameViewportClient::Draw(FViewport* InViewport, FCanvas* SceneCanvas)
// Create a view family for the game viewport
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
LocalPlayer->ViewportClient->Viewport,
Owner->GetWorld()->Scene,
LocalPlayer->ViewportClient->EngineShowFlags)
.SetRealtimeUpdate(false));
// Calculate a view where the player is
FVector ViewLocation;
FRotator ViewRotation;
const FSceneView* SceneView = LocalPlayer->CalcSceneView(&ViewFamily, /*out*/ ViewLocation, /*out*/ ViewRotation, LocalPlayer->ViewportClient->Viewport, nullptr, ViewIndex);
const int32 LOD = SceneView ? ComponentInterface->GetSceneProxy()->GetLOD(SceneView) : INDEX_NONE;
if (IsLODIndexValid(LOD))
{
return LOD;
}
}
return INDEX_NONE;
}
void FViewDebugInfo::ProcessPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo, const FViewInfo& View, FScene* Scene, IPrimitiveComponent* DebugComponentInterface)
{
if (!DebugComponentInterface || !DebugComponentInterface->IsRegistered() || !PrimitiveSceneInfo || !PrimitiveSceneInfo->Proxy)
{
return;
}
UObject* Owner = DebugComponentInterface->GetOwner();
if (!IsValid(Owner)) return;
FString FullName = DebugComponentInterface->GetName();
FPrimitiveStats Stats;
DebugComponentInterface->GetPrimitiveStats(Stats);
TArray<TWeakObjectPtr<UMaterialInterface>> Materials;
UMaterialInterface* OverlayMaterial = nullptr;
int32 CurrentLOD = PrimitiveSceneInfo->Proxy->GetLOD(&View);
if (const UPrimitiveComponent* DebugComponent = DebugComponentInterface->GetUObject<UPrimitiveComponent>())
{
const int32 NumMaterials = DebugComponent->GetNumMaterials();
Materials.Reserve(NumMaterials);
for (int32 Idx = 0; Idx < NumMaterials; Idx++)
{
if (UMaterialInterface* MaterialInterface = DebugComponent->GetMaterial(Idx))
{
Materials.Add(MaterialInterface);
}
}
if (const UMeshComponent* MeshComponent = Cast<UMeshComponent>(DebugComponent))
{
OverlayMaterial = MeshComponent->GetOverlayMaterial();
if (const USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(MeshComponent))
{
//CurrentLOD = SkinnedMeshComponent->GetPredictedLODLevel();
CurrentLOD = SkinnedMeshComponent->MeshObject->GetLOD();
}
}
}
const FPrimitiveInfo PrimitiveInfo = {
Owner,
PrimitiveSceneInfo->PrimitiveComponentId,
DebugComponentInterface,
DebugComponentInterface->GetUObject(),
PrimitiveSceneInfo,
MoveTemp(FullName),
MoveTemp(Stats),
MoveTemp(Materials),
OverlayMaterial,
CurrentLOD
};
Primitives.Add(PrimitiveSceneInfo->PrimitiveComponentId, PrimitiveInfo);
}
void FViewDebugInfo::DumpToCSV() const
{
const FString OutputPath = FPaths::ProfilingDir() / TEXT("Primitives") / FString::Printf(TEXT("PrimitivesDetailed-%s.csv"), *FDateTime::Now().ToString());
const bool bSuppressViewer = true;
FDiagnosticTableViewer DrawViewer(*OutputPath, bSuppressViewer);
DrawViewer.AddColumn(TEXT("Name"));
DrawViewer.AddColumn(TEXT("ActorClass"));
DrawViewer.AddColumn(TEXT("Actor"));
DrawViewer.AddColumn(TEXT("Location"));
DrawViewer.AddColumn(TEXT("NumMaterials"));
DrawViewer.AddColumn(TEXT("Materials"));
DrawViewer.AddColumn(TEXT("NumDraws"));
DrawViewer.AddColumn(TEXT("LOD"));
DrawViewer.AddColumn(TEXT("Triangles"));
DrawViewer.CycleRow();
FRWScopeLock ScopeLock(Lock, SLT_ReadOnly);
const FPrimitiveSceneInfo* LastPrimitiveSceneInfo = nullptr;
for (const TTuple<FPrimitiveComponentId, FPrimitiveInfo>& Entry : Primitives)
{
const FPrimitiveInfo& Primitive = Entry.Value;
if (Primitive.PrimitiveSceneInfo != LastPrimitiveSceneInfo)
{
const FPrimitiveLODStats* Stats = Primitive.GetCurrentLOD();
DrawViewer.AddColumn(*Primitive.Name);
DrawViewer.AddColumn(Primitive.Owner.IsValid() ? *Primitive.Owner->GetClass()->GetName() : TEXT(""));
DrawViewer.AddColumn(Primitive.Owner.IsValid() ? *Primitive.Owner->GetFullName() : TEXT(""));
DrawViewer.AddColumn(Primitive.IsPrimitiveValid() ?
*FString::Printf(TEXT("{%s}"), *Primitive.GetPrimitiveLocation().ToString()) : TEXT(""));
DrawViewer.AddColumn(*FString::Printf(TEXT("%d"), Primitive.Materials.Num()));
FString Materials = "[";
for (int i = 0; i < Primitive.Materials.Num(); i++)
{
if (Primitive.Materials[i].IsValid() && Primitive.Materials[i]->GetMaterial())
{
Materials += Primitive.Materials[i]->GetMaterial()->GetName();
}
else
{
Materials += "Null";
}
if (i < Primitive.Materials.Num() - 1)
{
Materials += ", ";
}
}
Materials += "]";
DrawViewer.AddColumn(*FString::Printf(TEXT("%s"), *Materials));
DrawViewer.AddColumn(*FString::Printf(TEXT("%d"), Stats ? Stats->GetDrawCount() : 0));
DrawViewer.AddColumn(*FString::Printf(TEXT("%d"), Stats ? Stats->LODIndex : -1));
DrawViewer.AddColumn(*FString::Printf(TEXT("%u"), Stats ? Stats->Triangles : 0));
DrawViewer.CycleRow();
LastPrimitiveSceneInfo = Primitive.PrimitiveSceneInfo;
}
}
}
void FViewDebugInfo::CaptureNextFrame()
{
ENQUEUE_RENDER_COMMAND(CmdShouldCaptureNextFrame)(
[this](const FRHICommandListImmediate& RHICmdList)
{
bShouldCaptureSingleFrame = true;
bShouldUpdate = true;
});
}
void FViewDebugInfo::EnableLiveCapture()
{
ENQUEUE_RENDER_COMMAND(CmdEnableLiveDebugCapture)(
[this](const FRHICommandListImmediate& RHICmdList)
{
bShouldCaptureSingleFrame = false;
bShouldUpdate = true;
});
}
void FViewDebugInfo::DisableLiveCapture()
{
ENQUEUE_RENDER_COMMAND(CmdDisableLiveDebugCapture)(
[this](const FRHICommandListImmediate& RHICmdList)
{
bShouldCaptureSingleFrame = false;
bShouldUpdate = false;
});
}
void FViewDebugInfo::ClearCaptureData()
{
ENQUEUE_RENDER_COMMAND(CmdShouldCaptureNextFrame)(
[this](const FRHICommandListImmediate& RHICmdList)
{
bShouldClearCapturedData = true;
});
}
bool FViewDebugInfo::HasEverUpdated() const
{
FRWScopeLock ScopeLock(Lock, SLT_ReadOnly);
return bHasEverUpdated;
}
bool FViewDebugInfo::IsOutOfDate() const
{
FRWScopeLock ScopeLock(Lock, SLT_ReadOnly);
return bIsOutdated;
}
void FViewDebugInfo::ProcessPrimitives(FScene* Scene, const FViewInfo& View, const FViewCommands& ViewCommands)
{
if (bDumpPrimitiveDrawCallsNextFrame)
{
bDumpPrimitiveDrawCallsNextFrame = false;
DumpDrawCallsToCSV();
}
{
FRWScopeLock ScopeLock(Lock, SLT_Write);
bIsOutdated = true;
if (bShouldClearCapturedData)
{
Primitives.Empty();
bShouldClearCapturedData = false;
}
if (!bShouldUpdate && !bDumpDetailedPrimitivesNextFrame)
{
return;
}
if (bShouldCaptureSingleFrame)
{
bShouldCaptureSingleFrame = false;
bShouldUpdate = false;
}
Primitives.Empty();
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
const int32 PrimitiveIndex = BitIt.GetIndex();
FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[PrimitiveIndex];
ProcessPrimitive(PrimitiveSceneInfo, View, Scene, PrimitiveSceneInfo->GetComponentInterfaceForDebugOnly());
}
bHasEverUpdated = true;
bIsOutdated = false;
}
AsyncTask(ENamedThreads::GameThread, [this]()
{
OnUpdate.Broadcast();
});
if (bDumpDetailedPrimitivesNextFrame)
{
DumpToCSV();
bDumpDetailedPrimitivesNextFrame = false;
}
}
void FViewDebugInfo::DumpDrawCallsToCSV()
{
const FString OutputPath = FPaths::ProfilingDir() / TEXT("Primitives") / FString::Printf(TEXT("Primitives-%s.csv"), *FDateTime::Now().ToString());
const bool bSuppressViewer = true;
FDiagnosticTableViewer DrawViewer(*OutputPath, bSuppressViewer);
DrawViewer.AddColumn(TEXT("Name"));
DrawViewer.AddColumn(TEXT("NumDraws"));
DrawViewer.CycleRow();
FRWScopeLock ScopeLock(Lock, SLT_ReadOnly);
const FPrimitiveSceneInfo* LastPrimitiveSceneInfo = nullptr;
for (const TTuple<FPrimitiveComponentId, FPrimitiveInfo>& Entry : Primitives)
{
const FPrimitiveInfo& Primitive = Entry.Value;
if (Primitive.PrimitiveSceneInfo != LastPrimitiveSceneInfo)
{
const FPrimitiveLODStats* Stats = Primitive.GetCurrentLOD();
DrawViewer.AddColumn(*Primitive.Name);
DrawViewer.AddColumn(*FString::Printf(TEXT("%d"), Stats ? Stats->GetDrawCount() : 0));
DrawViewer.CycleRow();
LastPrimitiveSceneInfo = Primitive.PrimitiveSceneInfo;
}
}
}
#endif