// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Misc/ScopeRWLock.h" #include "PrimitiveSceneInfo.h" #include "PrimitiveComponentId.h" #include "Components/ComponentInterfaces.h" #include "GameFramework/Actor.h" #if !UE_BUILD_SHIPPING // TODO: Decide whether or not the struct should be entirely stripped out of shipping class UMaterialInterface; class FScene; class FViewInfo; class FViewCommands; DECLARE_MULTICAST_DELEGATE(FOnUpdateViewDebugInfo); /** * A collection of debug data associated with the current on screen view. */ struct FViewDebugInfo { friend class FDrawPrimitiveDebuggerModule; private: static RENDERER_API FViewDebugInfo Instance; RENDERER_API FViewDebugInfo(); public: /** * Gets a reference to the view debug information that is used by the renderer. * @returns The debug information that is used by the renderer. */ static inline FViewDebugInfo& Get() { return Instance; } /** * Data collected about a single primitive being drawn to the screen. */ struct FPrimitiveInfo { TWeakObjectPtr Owner; FPrimitiveComponentId ComponentId; IPrimitiveComponent* ComponentInterface; TWeakObjectPtr ComponentUObject; FPrimitiveSceneInfo* PrimitiveSceneInfo; FString Name; FPrimitiveStats Stats; TArray> Materials; TWeakObjectPtr OverlayMaterial; int32 LODAtLastCapture; bool operator<(const FPrimitiveInfo& Other) const { // Sort by name to group similar assets together, then by exact primitives so we can ignore duplicates const int32 NameCompare = Name.Compare(Other.Name); if (NameCompare != 0) { return NameCompare < 0; } return PrimitiveSceneInfo < Other.PrimitiveSceneInfo; } bool IsPrimitiveValid() const { bool bValid = true; bValid &= Owner.IsValid(); bValid &= ComponentInterface != nullptr; if (bValid) { bValid &= ComponentUObject.IsValid(); if (bValid) { bValid &= !ComponentInterface->IsUnreachable(); } } return bValid; } FORCEINLINE bool HasLODs() const { return !Stats.LODStats.IsEmpty(); } FORCEINLINE bool IsLODIndexValid(int32 LOD) const { return LOD >= 0 && LOD < Stats.LODStats.Num(); } RENDERER_API int32 ComputeCurrentLODIndex(int32 PlayerIndex = 0, int32 ViewIndex = 0) const; FORCEINLINE FPrimitiveLODStats* GetCurrentLOD(int32 PlayerIndex = 0, int32 ViewIndex = 0) { int32 LOD = ComputeCurrentLODIndex(PlayerIndex, ViewIndex); if (!IsLODIndexValid(LOD)) LOD = LODAtLastCapture; return IsLODIndexValid(LOD) ? &Stats.LODStats[LOD] : nullptr; } FORCEINLINE const FPrimitiveLODStats* GetCurrentLOD(int32 PlayerIndex = 0, int32 ViewIndex = 0) const { int32 LOD = ComputeCurrentLODIndex(PlayerIndex, ViewIndex); if (!IsLODIndexValid(LOD)) LOD = LODAtLastCapture; return IsLODIndexValid(LOD) ? &Stats.LODStats[LOD] : nullptr; } FORCEINLINE FPrimitiveLODStats* GetLOD(int32 LOD) { return IsLODIndexValid(LOD) ? &Stats.LODStats[LOD] : nullptr; } FORCEINLINE const FPrimitiveLODStats* GetLOD(int32 LOD) const { return IsLODIndexValid(LOD) ? &Stats.LODStats[LOD] : nullptr; } FORCEINLINE UMaterialInterface* GetMaterial(uint16 Index) const { return Index < Materials.Num() ? Materials[Index].Get() : nullptr; } FORCEINLINE int32 GetNumLODs() const { return Stats.LODStats.Num(); } FORCEINLINE FString GetOwnerName() const { if (const AActor* Actor = Cast(Owner)) { return Actor->GetHumanReadableName(); } return ComponentInterface->GetOwnerName(); } FORCEINLINE FVector GetPrimitiveLocation() const { return ComponentInterface->GetTransform().GetLocation(); } }; private: bool bHasEverUpdated; bool bIsOutdated; bool bShouldUpdate; bool bShouldCaptureSingleFrame; bool bShouldClearCapturedData; FOnUpdateViewDebugInfo OnUpdate; mutable FRWLock Lock; TMap Primitives; RENDERER_API void ProcessPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo, const FViewInfo& View, FScene* Scene, IPrimitiveComponent* DebugComponent); RENDERER_API void CaptureNextFrame(); RENDERER_API void EnableLiveCapture(); RENDERER_API void DisableLiveCapture(); RENDERER_API void ClearCaptureData(); public: RENDERER_API void ProcessPrimitives(FScene* Scene, const FViewInfo& View, const FViewCommands& ViewCommands); /** * Writes the draw call count of all currently tracked primitives to a csv file. * The file will be stored in /Saved/Profiling/Primitives/... */ RENDERER_API void DumpDrawCallsToCSV(); /** * Writes detailed information about all currently tracked primitives to a csv file. * The file will be stored in /Saved/Profiling/Primitives/... */ RENDERER_API void DumpToCSV() const; /** * Performs an operation for each primitive currently tracked. * @param Action The action to perform for each primitive. */ template void ForEachPrimitive(CallableT Action) const { const FPrimitiveSceneInfo* LastPrimitiveSceneInfo = nullptr; FRWScopeLock ScopeLock(Lock, SLT_ReadOnly); for (const auto& [PrimitiveId, Primitive] : Primitives) { if (Primitive.PrimitiveSceneInfo != LastPrimitiveSceneInfo) { Invoke(Action, Primitive); LastPrimitiveSceneInfo = Primitive.PrimitiveSceneInfo; } } } /** * Checks if this debug information has ever been updated. * @returns True if the information has been updated at least once. */ RENDERER_API bool HasEverUpdated() const; /** * Checks if current information is from an older frame. * @returns True if the data in this object is outdated. */ RENDERER_API bool IsOutOfDate() const; template FDelegateHandle AddUpdateHandler(UserClass* UserObject, void (UserClass::*Func)()) { return OnUpdate.AddRaw(UserObject, Func); } FDelegateHandle AddUpdateHandler(void (*Func)()) { return OnUpdate.AddStatic(Func); } void RemoveUpdateHandler(const FDelegateHandle& Handle) { OnUpdate.Remove(Handle); } }; #endif